aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorkleidis <kleidis1@protonmail.com>2025-12-31 17:02:28 +0100
committercrueter <crueter@eden-emu.dev>2025-12-31 17:02:28 +0100
commit76be55bc2fd027b4950cf4ab90524007ed6f18a5 (patch)
tree7017936ff22c105be2133fc750f1e0538386f091 /src/android
parenta79eab956480102b88618f17d83595faf4ca606d (diff)
[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>
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/LaunchableSetting.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/LaunchableViewHolder.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt66
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt50
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt42
-rw-r--r--src/android/app/src/main/jni/android_settings.h4
-rw-r--r--src/android/app/src/main/res/menu/menu_overlay_options.xml5
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml4
-rw-r--r--src/android/app/src/main/res/values/strings.xml10
17 files changed, 332 insertions, 24 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 40c0af0b24..2dba024592 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
@@ -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
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 e173fbab8b..bd898251e6 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
@@ -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"),
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 d74ca69804..8ec498ad22 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
@@ -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")
;
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/LaunchableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/LaunchableSetting.kt
new file mode 100644
index 0000000000..eb25f0989f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/LaunchableSetting.kt
@@ -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
+}
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 73336abfb4..39fb435b4d 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
@@ -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"
@@ -366,6 +367,30 @@ abstract class SettingsItem(
)
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,
titleId = R.string.enable_input_overlay_auto_hide,
)
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 71a3e54cb3..576af8ece8 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
@@ -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,
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 5f31dd7ed2..ca5df58fe8 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
@@ -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)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/LaunchableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/LaunchableViewHolder.kt
new file mode 100644
index 0000000000..8336517559
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/LaunchableViewHolder.kt
@@ -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
+ }
+}
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 3f5abc0858..05134cffb1 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
@@ -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()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index 256e5968d1..d1252fc3c4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -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
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index fee3d04ee3..da85c875b5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -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)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
index 01f07e4f36..3a40bd7419 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt
@@ -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
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index bc3ff15b21..9943daa069 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -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()
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index e276f19284..eb8e7b77ea 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -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",
diff --git a/src/android/app/src/main/res/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml
index 71202bfe3f..8ed9fb78be 100644
--- a/src/android/app/src/main/res/menu/menu_overlay_options.xml
+++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml
@@ -16,6 +16,11 @@
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" />
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index 2f8c3fa0dd..2adc60a47c 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -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
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index f013821878..60135a808e 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -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>