From f1ce04818bad20cbd72a45bc433537b02de8c116 Mon Sep 17 00:00:00 2001 From: nyx Date: Wed, 24 Sep 2025 09:30:58 +0200 Subject: [PATCH] [android] input over(lay)haul 2: Individual scaling of buttons --- .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 244 +++++++++++++++--- .../overlay/InputOverlayDrawableDpad.kt | 6 +- .../overlay/InputOverlayDrawableJoystick.kt | 6 +- .../yuzu_emu/overlay/OverlayScaleDialog.kt | 124 +++++++++ .../yuzu_emu/overlay/model/OverlayControl.kt | 61 +++-- .../overlay/model/OverlayControlData.kt | 7 +- .../yuzu_emu/utils/DirectoryInitialization.kt | 7 +- .../app/src/main/jni/android_config.cpp | 6 +- .../app/src/main/jni/android_settings.h | 1 + .../app/src/main/jni/native_config.cpp | 13 +- .../main/res/layout/dialog_overlay_scale.xml | 74 ++++++ .../app/src/main/res/values/strings.xml | 1 + src/common/android/id_cache.cpp | 9 +- src/common/android/id_cache.h | 1 + 14 files changed, 491 insertions(+), 69 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt create mode 100644 src/android/app/src/main/res/layout/dialog_overlay_scale.xml 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..467b704da4 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,6 +830,7 @@ 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) @@ -860,6 +1027,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 +1092,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 +1179,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(R.id.resetButton) + val confirmButton = view.findViewById(R.id.confirmButton) + val cancelButton = view.findViewById(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, @IntegerRes val defaultPortraitPositionResources: Pair, - @IntegerRes val defaultFoldablePositionResources: Pair + @IntegerRes val defaultFoldablePositionResources: Pair, + 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 { @@ -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, var portraitPosition: Pair, - var foldablePosition: Pair + var foldablePosition: Pair, + var individualScale: Float ) { fun positionFromLayout(layout: OverlayLayout): Pair = 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 #include @@ -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(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(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 landscape_position; std::pair portrait_position; std::pair 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 @@ -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(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 @@ + + + + + + + + + + + + + + + + + + + + + \ 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 1545576ea8..7ea8425b76 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -850,6 +850,7 @@ Touchscreen Lock drawer Unlock drawer + Reset Loading settings… diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index e0edd006a5..77ad6f8c80 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -49,6 +49,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 +245,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 +499,7 @@ namespace Common::Android { reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); s_overlay_control_data_constructor = env->GetMethodID(overlay_control_data_class, "", - "(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 +510,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..f6a5998b83 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -67,6 +67,7 @@ jclass GetOverlayControlDataClass(); jmethodID GetOverlayControlDataConstructor(); jfieldID GetOverlayControlDataIdField(); jfieldID GetOverlayControlDataEnabledField(); +jfieldID GetOverlayControlDataIndividualScaleField(); jfieldID GetOverlayControlDataLandscapePositionField(); jfieldID GetOverlayControlDataPortraitPositionField(); jfieldID GetOverlayControlDataFoldablePositionField();