aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt255
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt124
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt7
-rw-r--r--src/android/app/src/main/jni/android_config.cpp6
-rw-r--r--src/android/app/src/main/jni/android_settings.h1
-rw-r--r--src/android/app/src/main/jni/native_config.cpp13
-rw-r--r--src/android/app/src/main/res/layout/dialog_overlay_scale.xml74
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
-rw-r--r--src/common/android/id_cache.cpp14
-rw-r--r--src/common/android/id_cache.h6
15 files changed, 506 insertions, 77 deletions
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 8162098caa..4487812ad1 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
@@ -924,6 +924,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
IntSetting.OVERLAY_OPACITY.reset()
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
+ binding.surfaceInputOverlay.resetIndividualControlScale()
}
}
@@ -1546,6 +1547,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
setControlScale(50)
setControlOpacity(100)
+ binding.surfaceInputOverlay.resetIndividualControlScale()
}
.show()
}
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 737e035840..9f050a5053 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -13,6 +13,8 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.VectorDrawable
import android.os.Build
+import android.os.Handler
+import android.os.Looper
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
+ private var scaleDialog: OverlayScaleDialog? = null
+ private var touchStartX = 0f
+ private var touchStartY = 0f
+ private var hasMoved = false
+ private val moveThreshold = 20f
+
private lateinit var windowInsets: WindowInsets
var layout = OverlayLayout.Landscape
@@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
buttonBeingConfigured = button
buttonBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
- buttonBeingConfigured!!.onConfigureTouch(event)
- invalidate()
- return true
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ buttonBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ return true
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
- // Persist button position by saving new place.
- saveControlPosition(
- buttonBeingConfigured!!.overlayControlData.id,
- buttonBeingConfigured!!.bounds.centerX(),
- buttonBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ showScaleDialog(
+ buttonBeingConfigured,
+ null,
+ null,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ saveControlPosition(
+ buttonBeingConfigured!!.overlayControlData.id,
+ buttonBeingConfigured!!.bounds.centerX(),
+ buttonBeingConfigured!!.bounds.centerY(),
+ individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale,
+ layout
+ )
+ }
buttonBeingConfigured = null
}
}
@@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
dpadBeingConfigured = dpad
dpadBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
- dpadBeingConfigured!!.onConfigureTouch(event)
- invalidate()
- return true
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ dpadBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ return true
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
- // Persist button position by saving new place.
- saveControlPosition(
- OverlayControl.COMBINED_DPAD.id,
- dpadBeingConfigured!!.bounds.centerX(),
- dpadBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ // This was a click, show scale dialog for dpad
+ showScaleDialog(
+ null,
+ dpadBeingConfigured,
+ null,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ // This was a move, save position
+ saveControlPosition(
+ OverlayControl.COMBINED_DPAD.id,
+ dpadBeingConfigured!!.bounds.centerX(),
+ dpadBeingConfigured!!.bounds.centerY(),
+ individuaScale = dpadBeingConfigured!!.individualScale,
+ layout
+ )
+ }
dpadBeingConfigured = null
}
}
@@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
) {
joystickBeingConfigured = joystick
joystickBeingConfigured!!.onConfigureTouch(event)
+ touchStartX = event.getX(pointerIndex)
+ touchStartY = event.getY(pointerIndex)
+ hasMoved = false
}
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
- joystickBeingConfigured!!.onConfigureTouch(event)
- invalidate()
+ val moveDistance = kotlin.math.sqrt(
+ (event.getX(pointerIndex) - touchStartX).let { it * it } +
+ (event.getY(pointerIndex) - touchStartY).let { it * it }
+ )
+
+ if (moveDistance > moveThreshold) {
+ hasMoved = true
+ joystickBeingConfigured!!.onConfigureTouch(event)
+ invalidate()
+ }
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
- saveControlPosition(
- joystickBeingConfigured!!.prefId,
- joystickBeingConfigured!!.bounds.centerX(),
- joystickBeingConfigured!!.bounds.centerY(),
- layout
- )
+ if (!hasMoved) {
+ showScaleDialog(
+ null,
+ null,
+ joystickBeingConfigured,
+ fingerPositionX,
+ fingerPositionY
+ )
+ } else {
+ saveControlPosition(
+ joystickBeingConfigured!!.prefId,
+ joystickBeingConfigured!!.bounds.centerX(),
+ joystickBeingConfigured!!.bounds.centerY(),
+ individuaScale = joystickBeingConfigured!!.individualScale,
+ layout
+ )
+ }
joystickBeingConfigured = null
}
}
@@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
invalidate()
}
- private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) {
+ private fun saveControlPosition(
+ id: String,
+ x: Int,
+ y: Int,
+ individuaScale: Float,
+ layout: OverlayLayout
+ ) {
val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
val min = windowSize.first
val max = windowSize.second
val overlayControlData = NativeConfig.getOverlayControlData()
val data = overlayControlData.firstOrNull { it.id == id }
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
+
when (layout) {
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
+
}
+
+ data?.individualScale = individuaScale
+
NativeConfig.setOverlayControlData(overlayControlData)
}
fun setIsInEditMode(editMode: Boolean) {
inEditMode = editMode
+ if (!editMode) {
+ scaleDialog?.dismiss()
+ scaleDialog = null
+ }
+ }
+
+ private fun showScaleDialog(
+ button: InputOverlayDrawableButton?,
+ dpad: InputOverlayDrawableDpad?,
+ joystick: InputOverlayDrawableJoystick?,
+ x: Int, y: Int
+ ) {
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ // prevent dialog from being spam opened
+ scaleDialog?.dismiss()
+
+
+ when {
+ button != null -> {
+ val buttonData =
+ overlayControlData.firstOrNull { it.id == button.overlayControlData.id }
+ if (buttonData != null) {
+ scaleDialog =
+ OverlayScaleDialog(context, button.overlayControlData) { newScale ->
+ saveControlPosition(
+ button.overlayControlData.id,
+ button.bounds.centerX(),
+ button.bounds.centerY(),
+ individuaScale = newScale,
+ layout
+ )
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, button.bounds.width(), button.bounds.height())
+
+ }
+ }
+
+ dpad != null -> {
+ val dpadData =
+ overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
+ if (dpadData != null) {
+ scaleDialog = OverlayScaleDialog(context, dpadData) { newScale ->
+ saveControlPosition(
+ OverlayControl.COMBINED_DPAD.id,
+ dpad.bounds.centerX(),
+ dpad.bounds.centerY(),
+ newScale,
+ layout
+ )
+
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, dpad.bounds.width(), dpad.bounds.height())
+
+ }
+ }
+
+ joystick != null -> {
+ val joystickData = overlayControlData.firstOrNull { it.id == joystick.prefId }
+ if (joystickData != null) {
+ scaleDialog = OverlayScaleDialog(context, joystickData) { newScale ->
+ saveControlPosition(
+ joystick.prefId,
+ joystick.bounds.centerX(),
+ joystick.bounds.centerY(),
+ individuaScale = newScale,
+ layout
+ )
+
+ refreshControls()
+ }
+
+ scaleDialog?.showDialog(x,y, joystick.bounds.width(), joystick.bounds.height())
+
+ }
+ }
+ }
}
+
/**
* Applies and saves all default values for the overlay
*/
@@ -664,12 +830,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
val overlayControlData = NativeConfig.getOverlayControlData()
overlayControlData.forEach {
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
+ it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!!
}
NativeConfig.setOverlayControlData(overlayControlData)
refreshControls()
}
+ fun resetIndividualControlScale() {
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ overlayControlData.forEach { data ->
+ val defaultControlData = OverlayControl.from(data.id) ?: return@forEach
+ data.individualScale = defaultControlData.defaultIndividualScaleResource
+ }
+ NativeConfig.setOverlayControlData(overlayControlData)
+ NativeConfig.saveGlobalConfig()
+ refreshControls()
+ }
+
private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
val overlayControlData = NativeConfig.getOverlayControlData()
for (data in overlayControlData) {
@@ -860,6 +1038,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ scale *= overlayControlData.individualScale
+
// Initialize the InputOverlayDrawableButton.
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
val pressedStateBitmap = getBitmap(context, pressedResId, scale)
@@ -922,11 +1103,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
// Resources handle for fetching the initial Drawable resource.
val res = context.resources
+ // Get the dpad control data for individual scale
+ val overlayControlData = NativeConfig.getOverlayControlData()
+ val dpadData = overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id }
+
// Decide scale based on button ID and user preference
var scale = 0.25f
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ if (dpadData != null) {
+ scale *= dpadData.individualScale
+ }
+
// Initialize the InputOverlayDrawableDpad.
val defaultStateBitmap =
getBitmap(context, defaultResId, scale)
@@ -1000,6 +1190,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
scale /= 100f
+ // Apply individual scale
+ scale *= overlayControlData.individualScale
+
// Initialize the InputOverlayDrawableJoystick.
val bitmapOuter = getBitmap(context, resOuter, scale)
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
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 0cb6ff2440..01f07e4f36 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -42,6 +42,8 @@ class InputOverlayDrawableDpad(
val width: Int
val height: Int
+ var individualScale: Float = 1.0f
+
private val defaultStateBitmap: BitmapDrawable
private val pressedOneDirectionStateBitmap: BitmapDrawable
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
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 4b07107fca..bc3ff15b21 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
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick(
val width: Int
val height: Int
+ var individualScale: Float = 1.0f
+
private var opacity: Int = 0
private var virtBounds: Rect
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt
new file mode 100644
index 0000000000..f489ef3b7c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.overlay
+
+import android.app.Dialog
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.WindowManager
+import android.widget.TextView
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.slider.Slider
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+
+class OverlayScaleDialog(
+ context: Context,
+ private val overlayControlData: OverlayControlData,
+ private val onScaleChanged: (Float) -> Unit
+) : Dialog(context) {
+
+ private var currentScale = overlayControlData.individualScale
+ private val originalScale = overlayControlData.individualScale
+ private lateinit var scaleValueText: TextView
+ private lateinit var scaleSlider: Slider
+
+ init {
+ setupDialog()
+ }
+
+ private fun setupDialog() {
+ val view = LayoutInflater.from(context).inflate(R.layout.dialog_overlay_scale, null)
+ setContentView(view)
+
+ window?.setBackgroundDrawable(null)
+
+ window?.apply {
+ attributes = attributes.apply {
+ flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv()
+ flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ }
+ }
+
+ scaleValueText = view.findViewById(R.id.scaleValueText)
+ scaleSlider = view.findViewById(R.id.scaleSlider)
+ val resetButton = view.findViewById<MaterialButton>(R.id.resetButton)
+ val confirmButton = view.findViewById<MaterialButton>(R.id.confirmButton)
+ val cancelButton = view.findViewById<MaterialButton>(R.id.cancelButton)
+
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ scaleSlider.value = currentScale
+
+ scaleSlider.addOnChangeListener { _, value, input ->
+ if (input) {
+ currentScale = value
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ }
+ }
+
+ scaleSlider.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
+ override fun onStartTrackingTouch(slider: Slider) {
+ // pass
+ }
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ onScaleChanged(currentScale)
+ }
+ })
+
+ resetButton.setOnClickListener {
+ currentScale = 1.0f
+ scaleSlider.value = 1.0f
+ scaleValueText.text = String.format("%.1fx", currentScale)
+ onScaleChanged(currentScale)
+ }
+
+ confirmButton.setOnClickListener {
+ overlayControlData.individualScale = currentScale
+ //slider value is already saved on touch dispatch but just to be sure
+ onScaleChanged(currentScale)
+ dismiss()
+ }
+
+ // both cancel button and back gesture should revert the scale change
+ cancelButton.setOnClickListener {
+ onScaleChanged(originalScale)
+ dismiss()
+ }
+
+ setOnCancelListener {
+ onScaleChanged(originalScale)
+ dismiss()
+ }
+ }
+
+ fun showDialog(anchorX: Int, anchorY: Int, anchorHeight: Int, anchorWidth: Int) {
+ show()
+
+ show()
+
+ // TODO: this calculation is a bit rough, improve it later on
+ window?.let { window ->
+ val layoutParams = window.attributes
+ layoutParams.gravity = Gravity.TOP or Gravity.START
+
+ val density = context.resources.displayMetrics.density
+ val dialogWidthPx = (320 * density).toInt()
+ val dialogHeightPx = (400 * density).toInt() // set your estimated dialog height
+
+ val screenHeight = context.resources.displayMetrics.heightPixels
+
+
+ layoutParams.x = anchorX + anchorWidth / 2 - dialogWidthPx / 2
+ layoutParams.y = anchorY + anchorHeight / 2 - dialogHeightPx / 2
+ layoutParams.width = dialogWidthPx
+
+
+ window.attributes = layoutParams
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
index a0eeadf4bc..10cc547d0b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model
@@ -12,126 +12,144 @@ enum class OverlayControl(
val defaultVisibility: Boolean,
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
@IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
- @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
+ @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>,
+ val defaultIndividualScaleResource: Float,
) {
BUTTON_A(
"button_a",
true,
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
- Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE),
+ 1.0f
),
BUTTON_B(
"button_b",
true,
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
- Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE),
+ 1.0f
),
BUTTON_X(
"button_x",
true,
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
- Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE),
+ 1.0f
),
BUTTON_Y(
"button_y",
true,
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
- Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE),
+ 1.0f
),
BUTTON_PLUS(
"button_plus",
true,
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
- Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE),
+ 1.0f
),
BUTTON_MINUS(
"button_minus",
true,
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
- Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE),
+ 1.0f
),
BUTTON_HOME(
"button_home",
false,
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
- Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE),
+ 1.0f
),
BUTTON_CAPTURE(
"button_capture",
false,
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
- Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE),
+ 1.0f
),
BUTTON_L(
"button_l",
true,
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
- Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE),
+ 1.0f
),
BUTTON_R(
"button_r",
true,
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
- Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE),
+ 1.0f
),
BUTTON_ZL(
"button_zl",
true,
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
- Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE),
+ 1.0f
),
BUTTON_ZR(
"button_zr",
true,
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
- Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE),
+ 1.0f
),
BUTTON_STICK_L(
"button_stick_l",
true,
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
- Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE),
+ 1.0f
),
BUTTON_STICK_R(
"button_stick_r",
true,
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
- Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
+ Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE),
+ 1.0f
),
STICK_L(
"stick_l",
true,
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
- Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
+ Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE),
+ 1.0f
),
STICK_R(
"stick_r",
true,
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
- Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
+ Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE),
+ 1.0f
),
COMBINED_DPAD(
"combined_dpad",
true,
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
- Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
+ Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE),
+ 1.0f
);
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
@@ -173,7 +191,8 @@ enum class OverlayControl(
defaultVisibility,
getDefaultPositionForLayout(OverlayLayout.Landscape),
getDefaultPositionForLayout(OverlayLayout.Portrait),
- getDefaultPositionForLayout(OverlayLayout.Foldable)
+ getDefaultPositionForLayout(OverlayLayout.Foldable),
+ defaultIndividualScaleResource
)
companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
index 26cfeb1db5..6cc5a59c98 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay.model
@@ -8,7 +8,8 @@ data class OverlayControlData(
var enabled: Boolean,
var landscapePosition: Pair<Double, Double>,
var portraitPosition: Pair<Double, Double>,
- var foldablePosition: Pair<Double, Double>
+ var foldablePosition: Pair<Double, Double>,
+ var individualScale: Float
) {
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
when (layout) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index de0794a17f..5325f688b6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -170,7 +170,8 @@ object DirectoryInitialization {
buttonEnabled,
Pair(landscapeXPosition, landscapeYPosition),
Pair(portraitXPosition, portraitYPosition),
- Pair(foldableXPosition, foldableYPosition)
+ Pair(foldableXPosition, foldableYPosition),
+ OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f
)
overlayControlDataMap[buttonId] = controlData
setOverlayData = true
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index a79a64afbb..41ac680d6b 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <common/logging/log.h>
#include <input_common/main.h>
@@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() {
ReadDoubleSetting(std::string("foldable\\x_position"));
control_data.foldable_position.second =
ReadDoubleSetting(std::string("foldable\\y_position"));
+ control_data.individual_scale = static_cast<float>(ReadDoubleSetting(std::string("individual_scale")));
AndroidSettings::values.overlay_control_data.push_back(control_data);
}
EndArray();
@@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() {
control_data.foldable_position.first);
WriteDoubleSetting(std::string("foldable\\y_position"),
control_data.foldable_position.second);
+ WriteDoubleSetting(std::string("individual_scale"), static_cast<double>(control_data.individual_scale));
}
EndArray();
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index cd18f1e5b3..16735531ee 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -24,6 +24,7 @@ namespace AndroidSettings {
std::pair<double, double> landscape_position;
std::pair<double, double> portrait_position;
std::pair<double, double> foldable_position;
+ float individual_scale;
};
struct Values {
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 0b26280c6c..e6021ed217 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <string>
@@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN
env->NewObject(Common::Android::GetOverlayControlDataClass(),
Common::Android::GetOverlayControlDataConstructor(),
Common::Android::ToJString(env, control_data.id), control_data.enabled,
- jlandscapePosition, jportraitPosition, jfoldablePosition);
+ jlandscapePosition, jportraitPosition, jfoldablePosition,
+ control_data.individual_scale);
+
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
}
return joverlayControlDataArray;
@@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
env,
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
+ float individual_scale = static_cast<float>(env->GetFloatField(
+ joverlayControlData, Common::Android::GetOverlayControlDataIndividualScaleField()));
+
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
Common::Android::GetJString(env, jidString), enabled, landscape_position,
- portrait_position, foldable_position});
+ portrait_position, foldable_position, individual_scale});
}
}
diff --git a/src/android/app/src/main/res/layout/dialog_overlay_scale.xml b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml
new file mode 100644
index 0000000000..9e047f211e
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="320dp"
+ android:layout_height="wrap_content"
+ app:cardCornerRadius="16dp"
+ app:cardElevation="8dp"
+ app:cardBackgroundColor="#CC222222">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="12dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/scaleValueText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="1.0x"
+ android:textAppearance="?attr/textAppearanceBody1"
+ android:gravity="center"
+ android:layout_marginBottom="4dp"
+ android:textColor="#FFFFFF"
+ android:textStyle="bold" />
+
+ <com.google.android.material.slider.Slider
+ android:id="@+id/scaleSlider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:valueFrom="0.5"
+ android:valueTo="4.0"
+ android:stepSize="0.1"
+ android:value="1.0"
+ android:layout_marginBottom="8dp"
+ app:trackColorActive="@color/eden_border_gradient_start"
+ app:trackColorInactive="@color/eden_border_gradient_end"
+ app:tickColor="@color/eden_border_gradient_start"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="end">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/resetButton"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/reset"
+ android:layout_marginEnd="4dp" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/cancelButton"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@android:string/cancel"
+ android:layout_marginEnd="4dp" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/confirmButton"
+ style="@style/Widget.Material3.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:backgroundTint="@color/eden_button_secondary_bg"
+ android:textColor="@color/eden_border_gradient_end"
+ android:text="@string/confirm" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView> \ No newline at end of file
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c05420a17f..d41246b184 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -855,6 +855,7 @@
<string name="touchscreen">Touchscreen</string>
<string name="lock_drawer">Lock drawer</string>
<string name="unlock_drawer">Unlock drawer</string>
+ <string name="reset">Reset</string>
<string name="load_settings">Loading settings…</string>
diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp
index e0edd006a5..1198833996 100644
--- a/src/common/android/id_cache.cpp
+++ b/src/common/android/id_cache.cpp
@@ -1,7 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-// SPDX-FileCopyrightText: 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <jni.h>
@@ -49,6 +46,7 @@ static jclass s_overlay_control_data_class;
static jmethodID s_overlay_control_data_constructor;
static jfieldID s_overlay_control_data_id_field;
static jfieldID s_overlay_control_data_enabled_field;
+static jfieldID s_overlay_control_data_individual_scale_field;
static jfieldID s_overlay_control_data_landscape_position_field;
static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field;
@@ -244,6 +242,10 @@ namespace Common::Android {
return s_overlay_control_data_enabled_field;
}
+ jfieldID GetOverlayControlDataIndividualScaleField() {
+ return s_overlay_control_data_individual_scale_field;
+ }
+
jfieldID GetOverlayControlDataLandscapePositionField() {
return s_overlay_control_data_landscape_position_field;
}
@@ -494,7 +496,7 @@ namespace Common::Android {
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
s_overlay_control_data_constructor =
env->GetMethodID(overlay_control_data_class, "<init>",
- "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
+ "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V");
s_overlay_control_data_id_field =
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
s_overlay_control_data_enabled_field =
@@ -505,6 +507,8 @@ namespace Common::Android {
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
s_overlay_control_data_foldable_position_field =
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
+ s_overlay_control_data_individual_scale_field =
+ env->GetFieldID(overlay_control_data_class, "individualScale", "F");
env->DeleteLocalRef(overlay_control_data_class);
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h
index c56ffcf5c6..6b48f471b7 100644
--- a/src/common/android/id_cache.h
+++ b/src/common/android/id_cache.h
@@ -1,7 +1,4 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-// SPDX-FileCopyrightText: 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -67,6 +64,7 @@ jclass GetOverlayControlDataClass();
jmethodID GetOverlayControlDataConstructor();
jfieldID GetOverlayControlDataIdField();
jfieldID GetOverlayControlDataEnabledField();
+jfieldID GetOverlayControlDataIndividualScaleField();
jfieldID GetOverlayControlDataLandscapePositionField();
jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField();