[android, inputOverlay] Add snap to grid option and allow editing the overlay without opening a game (#3234)

The new changes are in the input overlay section

Known issues:

- Auto hide, also hides the overlay in gameless edit mode
- Same goes for the controller auto hide option

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3234
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: kleidis <kleidis1@protonmail.com>
Co-committed-by: kleidis <kleidis1@protonmail.com>
This commit is contained in:
kleidis 2025-12-31 17:02:28 +01:00 committed by crueter
parent a79eab9564
commit 76be55bc2f
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
17 changed files with 332 additions and 24 deletions

View File

@ -639,6 +639,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
companion object {
const val EXTRA_SELECTED_GAME = "SelectedGame"
const val EXTRA_OVERLAY_GAMELESS_EDIT_MODE = "overlayGamelessEditMode"
fun stopForegroundService(activity: Activity) {
val startIntent = Intent(activity, ForegroundService::class.java)
@ -652,6 +653,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
activity.startActivity(launcher)
}
fun launchForOverlayEdit(context: Context): Intent {
return Intent(context, EmulationActivity::class.java).apply {
putExtra(EXTRA_OVERLAY_GAMELESS_EDIT_MODE, true)
}
}
private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
if (view == null) {
return true

View File

@ -40,6 +40,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
DPAD_SLIDE("dpad_slide"),
HAPTIC_FEEDBACK("haptic_feedback"),
SHOW_INPUT_OVERLAY("show_input_overlay"),
OVERLAY_SNAP_TO_GRID("overlay_snap_to_grid"),
TOUCHSCREEN("touchscreen"),
AIRPLANE_MODE("airplane_mode"),

View File

@ -64,6 +64,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
MY_PAGE_APPLET("my_page_applet_mode"),
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
OVERLAY_GRID_SIZE("overlay_grid_size"),
DEBUG_KNOBS("debug_knobs")
;

View File

@ -0,0 +1,20 @@
// 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 android.content.Intent
import androidx.annotation.StringRes
/**
* A settings item that launches an intent when clicked.
*/
class LaunchableSetting(
@StringRes titleId: Int = 0,
titleString: String = "",
@StringRes descriptionId: Int = 0,
descriptionString: String = "",
val launchIntent: (android.content.Context) -> Intent
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
override val type = SettingsItem.TYPE_LAUNCHABLE
}

View File

@ -97,6 +97,7 @@ abstract class SettingsItem(
const val TYPE_INPUT_PROFILE = 10
const val TYPE_STRING_INPUT = 11
const val TYPE_SPINBOX = 12
const val TYPE_LAUNCHABLE = 13
const val FASTMEM_COMBINED = "fastmem_combined"
@ -364,6 +365,30 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_INPUT_OVERLAY,
titleId = R.string.show_input_overlay,
descriptionId = R.string.show_input_overlay_description
)
)
put(
SwitchSetting(
BooleanSetting.OVERLAY_SNAP_TO_GRID,
titleId = R.string.overlay_snap_to_grid,
descriptionId = R.string.overlay_snap_to_grid_description
)
)
put(
SliderSetting(
IntSetting.OVERLAY_GRID_SIZE,
titleId = R.string.overlay_grid_size,
descriptionId = R.string.overlay_grid_size_description,
min = 16,
max = 128,
units = "px"
)
)
put(
SwitchSetting(
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,

View File

@ -93,6 +93,9 @@ class SettingsAdapter(
StringInputViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_LAUNCHABLE -> {
LaunchableViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
else -> {
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
}
@ -209,6 +212,11 @@ class SettingsAdapter(
fragment.view?.findNavController()?.navigate(action)
}
fun onLaunchableClick(item: LaunchableSetting) {
val intent = item.launchIntent(context)
fragment.requireActivity().startActivity(intent)
}
fun onInputProfileClick(item: InputProfileSetting, position: Int) {
InputProfileDialogFragment.newInstance(
settingsViewModel,

View File

@ -10,6 +10,7 @@ import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity
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.NativeAnalog
@ -294,6 +295,19 @@ class SettingsFragmentPresenter(
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(BooleanSetting.SHOW_INPUT_OVERLAY.key)
add(BooleanSetting.OVERLAY_SNAP_TO_GRID.key)
add(IntSetting.OVERLAY_GRID_SIZE.key)
add(
LaunchableSetting(
titleId = R.string.edit_overlay_layout,
descriptionId = R.string.edit_overlay_layout_description,
launchIntent = { context ->
EmulationActivity.launchForOverlayEdit(context)
}
)
)
add(HeaderSetting(R.string.input_overlay_behavior))
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)

View File

@ -0,0 +1,42 @@
// 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.LaunchableSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class LaunchableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: LaunchableSetting
override fun bind(item: SettingsItem) {
setting = item as LaunchableSetting
binding.textSettingName.text = setting.title
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
binding.textSettingDescription.text = setting.description
binding.textSettingValue.setVisible(true)
binding.textSettingValue.text = ""
binding.textSettingValue.setCompoundDrawablesRelativeWithIntrinsicBounds(
0, 0, R.drawable.ic_arrow_forward, 0
)
binding.buttonClear.setVisible(false)
}
override fun onClick(clicked: View) {
adapter.onLaunchableClick(setting)
}
override fun onLongClick(clicked: View): Boolean {
// no-op
return true
}
}

View File

@ -201,6 +201,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onCreate(savedInstanceState)
updateOrientation()
if (args.overlayGamelessEditMode) {
return
}
val intent = requireActivity().intent
val intentUri: Uri? = intent.data
intentGame = null
@ -558,6 +562,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return
}
if (args.overlayGamelessEditMode) {
setupOverlayGamelessEditMode()
return
}
if (game == null) {
Log.warning(
"[EmulationFragment] Game not yet initialized in onViewCreated - will be set up by async intent handler"
@ -568,6 +577,39 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
completeViewSetup()
}
private fun setupOverlayGamelessEditMode() {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.refreshControls(gameless = true)
}
binding.doneControlConfig.setOnClickListener {
finishOverlayGamelessEditMode()
}
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 gameless edit mode, back = done
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
finishOverlayGamelessEditMode()
}
}
)
}
private fun finishOverlayGamelessEditMode() {
binding.surfaceInputOverlay.setIsInEditMode(false)
NativeConfig.saveGlobalConfig()
requireActivity().finish()
}
private fun completeViewSetup() {
if (_binding == null || game == null) {
return
@ -900,9 +942,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
b.surfaceInputOverlay.setVisible(visible = false, gone = false)
}
} else {
b.surfaceInputOverlay.setVisible(
val shouldShowOverlay = if (args.overlayGamelessEditMode) {
true
} else {
showInputOverlay && emulationViewModel.emulationStarted.value
)
}
b.surfaceInputOverlay.setVisible(shouldShowOverlay)
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
b.surfaceInputOverlay.layout = OverlayLayout.Portrait
@ -1531,6 +1576,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
findItem(R.id.menu_show_overlay).isChecked =
BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
findItem(R.id.menu_snap_to_grid).isChecked =
BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()
findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
}
@ -1559,6 +1606,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
R.id.menu_snap_to_grid -> {
it.isChecked = !it.isChecked
BooleanSetting.OVERLAY_SNAP_TO_GRID.setBoolean(it.isChecked)
binding.surfaceInputOverlay.invalidate()
true
}
R.id.menu_adjust_overlay -> {
adjustOverlay()
true
@ -1942,6 +1996,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
fun handleScreenTap(isLongTap: Boolean) {
if (binding.surfaceInputOverlay.isGamelessMode()) {
return
}
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
@ -1963,6 +2021,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun initializeOverlayAutoHide() {
if (binding.surfaceInputOverlay.isGamelessMode()) {
return
}
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()

View File

@ -8,6 +8,8 @@ import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.Drawable
@ -50,6 +52,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
private var inEditMode = false
private var gamelessMode = false
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
@ -60,6 +63,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private var hasMoved = false
private val moveThreshold = 20f
private val gridPaint = Paint().apply {
color = Color.argb(60, 255, 255, 255)
strokeWidth = 1f
style = Paint.Style.STROKE
}
private lateinit var windowInsets: WindowInsets
var layout = OverlayLayout.Landscape
@ -91,6 +100,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
override fun draw(canvas: Canvas) {
super.draw(canvas)
// Draw grid when in edit mode and snap-to-grid is enabled
if (inEditMode && BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
drawGrid(canvas)
}
for (button in overlayButtons) {
button.draw(canvas)
}
@ -102,6 +117,26 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
}
private fun drawGrid(canvas: Canvas) {
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
val width = canvas.width
val height = canvas.height
// Draw vertical lines
var x = 0
while (x <= width) {
canvas.drawLine(x.toFloat(), 0f, x.toFloat(), height.toFloat(), gridPaint)
x += gridSize
}
// Draw horizontal lines
var y = 0
while (y <= height) {
canvas.drawLine(0f, y.toFloat(), width.toFloat(), y.toFloat(), gridPaint)
y += gridSize
}
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
if (inEditMode) {
return onTouchWhileEditing(event)
@ -668,14 +703,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
}
fun refreshControls() {
fun refreshControls(gameless: Boolean = false) {
// Store gameless mode if set to true
if (gameless) {
gamelessMode = true
}
// Remove all the overlay buttons from the HashSet.
overlayButtons.clear()
overlayDpads.clear()
overlayJoysticks.clear()
// Add all the enabled overlay items back to the HashSet.
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
if (gamelessMode || BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
addOverlayControls(layout)
}
invalidate()
@ -712,9 +752,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
if (!editMode) {
scaleDialog?.dismiss()
scaleDialog = null
gamelessMode = false
}
invalidate()
}
fun isGamelessMode(): Boolean = gamelessMode
private fun showScaleDialog(
button: InputOverlayDrawableButton?,
dpad: InputOverlayDrawableDpad?,
@ -867,6 +912,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
companion object {
// Increase this number every time there is a breaking change to every overlay layout
const val OVERLAY_VERSION = 1

View File

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -11,6 +14,8 @@ import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
/**
@ -121,11 +126,23 @@ class InputOverlayDrawableButton(
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionX)
} else {
controlPositionX
}
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionY)
} else {
controlPositionY
}
setBounds(
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
finalX,
finalY,
width + finalX,
height + finalY
)
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
@ -134,6 +151,11 @@ class InputOverlayDrawableButton(
return true
}
private fun snapToGrid(value: Int): Int {
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
return ((value + gridSize / 2) / gridSize) * gridSize
}
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
defaultStateBitmap.setBounds(left, top, right, bottom)
pressedStateBitmap.setBounds(left, top, right, bottom)

View File

@ -11,6 +11,8 @@ import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
/**
* Custom [BitmapDrawable] that is capable
@ -229,11 +231,23 @@ class InputOverlayDrawableDpad(
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionX)
} else {
controlPositionX
}
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionY)
} else {
controlPositionY
}
setBounds(
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
finalX,
finalY,
width + finalX,
height + finalY
)
previousTouchX = fingerPositionX
previousTouchY = fingerPositionY
@ -242,6 +256,11 @@ class InputOverlayDrawableDpad(
return true
}
private fun snapToGrid(value: Int): Int {
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
return ((value + gridSize / 2) / gridSize) * gridSize
}
fun setPosition(x: Int, y: Int) {
controlPositionX = x
controlPositionY = y

View File

@ -17,6 +17,7 @@ import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
import org.yuzu.yuzu_emu.features.input.model.NativeButton
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
/**
* Custom [BitmapDrawable] that is capable
@ -213,25 +214,37 @@ class InputOverlayDrawableJoystick(
MotionEvent.ACTION_MOVE -> {
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionX)
} else {
controlPositionX
}
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
snapToGrid(controlPositionY)
} else {
controlPositionY
}
bounds = Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
finalX,
finalY,
outerBitmap.intrinsicWidth + finalX,
outerBitmap.intrinsicHeight + finalY
)
virtBounds = Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
finalX,
finalY,
outerBitmap.intrinsicWidth + finalX,
outerBitmap.intrinsicHeight + finalY
)
setInnerBounds()
bounds = Rect(
Rect(
controlPositionX,
controlPositionY,
outerBitmap.intrinsicWidth + controlPositionX,
outerBitmap.intrinsicHeight + controlPositionY
finalX,
finalY,
outerBitmap.intrinsicWidth + finalX,
outerBitmap.intrinsicHeight + finalY
)
)
previousTouchX = fingerPositionX
@ -242,6 +255,11 @@ class InputOverlayDrawableJoystick(
return true
}
private fun snapToGrid(value: Int): Int {
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
return ((value + gridSize / 2) / gridSize) * gridSize
}
private fun setInnerBounds() {
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()

View File

@ -146,6 +146,10 @@ namespace AndroidSettings {
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
Settings::Category::Overlay};
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
Settings::Category::Overlay};
Settings::Setting<s32> overlay_grid_size{linkage, 32, "overlay_grid_size",
Settings::Category::Overlay};
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen",
Settings::Category::Overlay};
Settings::Setting<s32> lock_drawer{linkage, false, "lock_drawer",

View File

@ -15,6 +15,11 @@
android:id="@+id/menu_edit_overlay"
android:title="@string/emulation_touch_overlay_edit" />
<item
android:id="@+id/menu_snap_to_grid"
android:title="@string/emulation_snap_to_grid"
android:checkable="true" />
<item
android:id="@+id/menu_adjust_overlay"
android:title="@string/emulation_control_adjust" />

View File

@ -19,6 +19,10 @@
android:name="custom"
app:argType="boolean"
android:defaultValue="false" />
<argument
android:name="overlayGamelessEditMode"
app:argType="boolean"
android:defaultValue="false" />
</fragment>
<activity

View File

@ -23,6 +23,13 @@
<!-- Input Overlay -->
<string name="show_input_overlay">Show Input Overlay</string>
<string name="show_input_overlay_description">Display touch controls overlay during emulation</string>
<string name="overlay_snap_to_grid">Snap to Grid</string>
<string name="overlay_snap_to_grid_description">Snap overlay controls to a grid when editing</string>
<string name="overlay_grid_size">Grid Size</string>
<string name="overlay_grid_size_description">Size of the grid cells in pixels</string>
<string name="input_overlay_behavior">Behavior</string>
<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>
@ -31,6 +38,8 @@
<string name="input_overlay_options">Input Overlay</string>
<string name="input_overlay_options_description">Configure on-screen controls</string>
<string name="edit_overlay_layout">Edit Overlay Layout</string>
<string name="edit_overlay_layout_description">Adjust the position and scale of on-screen controls</string>
<!-- Stats Overlay settings -->
@ -841,6 +850,7 @@
<string name="emulation_control_opacity">Opacity</string>
<string name="emulation_touch_overlay_reset">Reset overlay</string>
<string name="emulation_touch_overlay_edit">Edit overlay</string>
<string name="emulation_snap_to_grid">Snap to grid</string>
<string name="emulation_pause">Pause emulation</string>
<string name="emulation_unpause">Unpause emulation</string>
<string name="emulation_input_overlay">Overlay options</string>