[android] input over(lay)haul 2: Individual scaling of buttons
Some checks failed
eden-license / license-header (pull_request) Failing after 23s
Some checks failed
eden-license / license-header (pull_request) Failing after 23s
This commit is contained in:
parent
f5bb07341a
commit
f1ce04818b
14 changed files with 491 additions and 69 deletions
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.VectorDrawable
|
import android.graphics.drawable.VectorDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = 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
|
private lateinit var windowInsets: WindowInsets
|
||||||
|
|
||||||
var layout = OverlayLayout.Landscape
|
var layout = OverlayLayout.Landscape
|
||||||
|
@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
buttonBeingConfigured = button
|
buttonBeingConfigured = button
|
||||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) {
|
||||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(event.getX(pointerIndex) - touchStartX).let { it * it } +
|
||||||
return true
|
(event.getY(pointerIndex) - touchStartY).let { it * it }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moveDistance > moveThreshold) {
|
||||||
|
hasMoved = true
|
||||||
|
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
|
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
|
||||||
// Persist button position by saving new place.
|
if (!hasMoved) {
|
||||||
saveControlPosition(
|
showScaleDialog(
|
||||||
buttonBeingConfigured!!.overlayControlData.id,
|
buttonBeingConfigured,
|
||||||
buttonBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
buttonBeingConfigured!!.bounds.centerY(),
|
null,
|
||||||
layout
|
fingerPositionX,
|
||||||
)
|
fingerPositionY
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
saveControlPosition(
|
||||||
|
buttonBeingConfigured!!.overlayControlData.id,
|
||||||
|
buttonBeingConfigured!!.bounds.centerX(),
|
||||||
|
buttonBeingConfigured!!.bounds.centerY(),
|
||||||
|
individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale,
|
||||||
|
layout
|
||||||
|
)
|
||||||
|
}
|
||||||
buttonBeingConfigured = null
|
buttonBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
dpadBeingConfigured = dpad
|
dpadBeingConfigured = dpad
|
||||||
dpadBeingConfigured!!.onConfigureTouch(event)
|
dpadBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) {
|
||||||
dpadBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(event.getX(pointerIndex) - touchStartX).let { it * it } +
|
||||||
return true
|
(event.getY(pointerIndex) - touchStartY).let { it * it }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moveDistance > moveThreshold) {
|
||||||
|
hasMoved = true
|
||||||
|
dpadBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
|
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
|
||||||
// Persist button position by saving new place.
|
if (!hasMoved) {
|
||||||
saveControlPosition(
|
// This was a click, show scale dialog for dpad
|
||||||
OverlayControl.COMBINED_DPAD.id,
|
showScaleDialog(
|
||||||
dpadBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
dpadBeingConfigured!!.bounds.centerY(),
|
dpadBeingConfigured,
|
||||||
layout
|
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
|
dpadBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
) {
|
) {
|
||||||
joystickBeingConfigured = joystick
|
joystickBeingConfigured = joystick
|
||||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
joystickBeingConfigured!!.onConfigureTouch(event)
|
||||||
|
touchStartX = event.getX(pointerIndex)
|
||||||
|
touchStartY = event.getY(pointerIndex)
|
||||||
|
hasMoved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
|
MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) {
|
||||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
val moveDistance = kotlin.math.sqrt(
|
||||||
invalidate()
|
(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_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
|
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
|
||||||
saveControlPosition(
|
if (!hasMoved) {
|
||||||
joystickBeingConfigured!!.prefId,
|
showScaleDialog(
|
||||||
joystickBeingConfigured!!.bounds.centerX(),
|
null,
|
||||||
joystickBeingConfigured!!.bounds.centerY(),
|
null,
|
||||||
layout
|
joystickBeingConfigured,
|
||||||
)
|
fingerPositionX,
|
||||||
|
fingerPositionY
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
saveControlPosition(
|
||||||
|
joystickBeingConfigured!!.prefId,
|
||||||
|
joystickBeingConfigured!!.bounds.centerX(),
|
||||||
|
joystickBeingConfigured!!.bounds.centerY(),
|
||||||
|
individuaScale = joystickBeingConfigured!!.individualScale,
|
||||||
|
layout
|
||||||
|
)
|
||||||
|
}
|
||||||
joystickBeingConfigured = null
|
joystickBeingConfigured = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
invalidate()
|
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 windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
|
||||||
val min = windowSize.first
|
val min = windowSize.first
|
||||||
val max = windowSize.second
|
val max = windowSize.second
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
val data = overlayControlData.firstOrNull { it.id == id }
|
val data = overlayControlData.firstOrNull { it.id == id }
|
||||||
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
|
val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
|
||||||
|
|
||||||
when (layout) {
|
when (layout) {
|
||||||
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
|
OverlayLayout.Landscape -> data?.landscapePosition = newPosition
|
||||||
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
|
OverlayLayout.Portrait -> data?.portraitPosition = newPosition
|
||||||
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
|
OverlayLayout.Foldable -> data?.foldablePosition = newPosition
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data?.individualScale = individuaScale
|
||||||
|
|
||||||
NativeConfig.setOverlayControlData(overlayControlData)
|
NativeConfig.setOverlayControlData(overlayControlData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setIsInEditMode(editMode: Boolean) {
|
fun setIsInEditMode(editMode: Boolean) {
|
||||||
inEditMode = editMode
|
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
|
* Applies and saves all default values for the overlay
|
||||||
*/
|
*/
|
||||||
|
@ -664,6 +830,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
overlayControlData.forEach {
|
overlayControlData.forEach {
|
||||||
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
|
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
|
||||||
|
it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!!
|
||||||
}
|
}
|
||||||
NativeConfig.setOverlayControlData(overlayControlData)
|
NativeConfig.setOverlayControlData(overlayControlData)
|
||||||
|
|
||||||
|
@ -860,6 +1027,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
scale *= overlayControlData.individualScale
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableButton.
|
// Initialize the InputOverlayDrawableButton.
|
||||||
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
||||||
val pressedStateBitmap = getBitmap(context, pressedResId, 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.
|
// Resources handle for fetching the initial Drawable resource.
|
||||||
val res = context.resources
|
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
|
// Decide scale based on button ID and user preference
|
||||||
var scale = 0.25f
|
var scale = 0.25f
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
if (dpadData != null) {
|
||||||
|
scale *= dpadData.individualScale
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableDpad.
|
// Initialize the InputOverlayDrawableDpad.
|
||||||
val defaultStateBitmap =
|
val defaultStateBitmap =
|
||||||
getBitmap(context, defaultResId, scale)
|
getBitmap(context, defaultResId, scale)
|
||||||
|
@ -1000,6 +1179,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
|
||||||
scale /= 100f
|
scale /= 100f
|
||||||
|
|
||||||
|
// Apply individual scale
|
||||||
|
scale *= overlayControlData.individualScale
|
||||||
|
|
||||||
// Initialize the InputOverlayDrawableJoystick.
|
// Initialize the InputOverlayDrawableJoystick.
|
||||||
val bitmapOuter = getBitmap(context, resOuter, scale)
|
val bitmapOuter = getBitmap(context, resOuter, scale)
|
||||||
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ class InputOverlayDrawableDpad(
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|
||||||
|
var individualScale: Float = 1.0f
|
||||||
|
|
||||||
private val defaultStateBitmap: BitmapDrawable
|
private val defaultStateBitmap: BitmapDrawable
|
||||||
private val pressedOneDirectionStateBitmap: BitmapDrawable
|
private val pressedOneDirectionStateBitmap: BitmapDrawable
|
||||||
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
|
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick(
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|
||||||
|
var individualScale: Float = 1.0f
|
||||||
|
|
||||||
private var opacity: Int = 0
|
private var opacity: Int = 0
|
||||||
|
|
||||||
private var virtBounds: Rect
|
private var virtBounds: Rect
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay.model
|
package org.yuzu.yuzu_emu.overlay.model
|
||||||
|
|
||||||
|
@ -12,126 +12,144 @@ enum class OverlayControl(
|
||||||
val defaultVisibility: Boolean,
|
val defaultVisibility: Boolean,
|
||||||
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
|
@IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
|
||||||
@IntegerRes val defaultPortraitPositionResources: 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(
|
||||||
"button_a",
|
"button_a",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
|
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_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(
|
||||||
"button_b",
|
"button_b",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
|
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_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(
|
||||||
"button_x",
|
"button_x",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
|
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_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(
|
||||||
"button_y",
|
"button_y",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
|
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_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(
|
||||||
"button_plus",
|
"button_plus",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
|
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_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(
|
||||||
"button_minus",
|
"button_minus",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
|
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_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(
|
||||||
"button_home",
|
"button_home",
|
||||||
false,
|
false,
|
||||||
Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
|
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_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(
|
||||||
"button_capture",
|
"button_capture",
|
||||||
false,
|
false,
|
||||||
Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
|
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_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(
|
||||||
"button_l",
|
"button_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
|
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_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(
|
||||||
"button_r",
|
"button_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
|
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_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(
|
||||||
"button_zl",
|
"button_zl",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
|
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_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(
|
||||||
"button_zr",
|
"button_zr",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
|
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_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(
|
||||||
"button_stick_l",
|
"button_stick_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
|
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_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(
|
||||||
"button_stick_r",
|
"button_stick_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
|
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_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(
|
||||||
"stick_l",
|
"stick_l",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
|
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_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(
|
||||||
"stick_r",
|
"stick_r",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
|
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_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(
|
||||||
"combined_dpad",
|
"combined_dpad",
|
||||||
true,
|
true,
|
||||||
Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
|
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_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> {
|
fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
|
||||||
|
@ -173,7 +191,8 @@ enum class OverlayControl(
|
||||||
defaultVisibility,
|
defaultVisibility,
|
||||||
getDefaultPositionForLayout(OverlayLayout.Landscape),
|
getDefaultPositionForLayout(OverlayLayout.Landscape),
|
||||||
getDefaultPositionForLayout(OverlayLayout.Portrait),
|
getDefaultPositionForLayout(OverlayLayout.Portrait),
|
||||||
getDefaultPositionForLayout(OverlayLayout.Foldable)
|
getDefaultPositionForLayout(OverlayLayout.Foldable),
|
||||||
|
defaultIndividualScaleResource
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay.model
|
package org.yuzu.yuzu_emu.overlay.model
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ data class OverlayControlData(
|
||||||
var enabled: Boolean,
|
var enabled: Boolean,
|
||||||
var landscapePosition: Pair<Double, Double>,
|
var landscapePosition: Pair<Double, Double>,
|
||||||
var portraitPosition: 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> =
|
fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
|
||||||
when (layout) {
|
when (layout) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
@ -170,7 +170,8 @@ object DirectoryInitialization {
|
||||||
buttonEnabled,
|
buttonEnabled,
|
||||||
Pair(landscapeXPosition, landscapeYPosition),
|
Pair(landscapeXPosition, landscapeYPosition),
|
||||||
Pair(portraitXPosition, portraitYPosition),
|
Pair(portraitXPosition, portraitYPosition),
|
||||||
Pair(foldableXPosition, foldableYPosition)
|
Pair(foldableXPosition, foldableYPosition),
|
||||||
|
OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f
|
||||||
)
|
)
|
||||||
overlayControlDataMap[buttonId] = controlData
|
overlayControlDataMap[buttonId] = controlData
|
||||||
setOverlayData = true
|
setOverlayData = true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <common/logging/log.h>
|
#include <common/logging/log.h>
|
||||||
#include <input_common/main.h>
|
#include <input_common/main.h>
|
||||||
|
@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() {
|
||||||
ReadDoubleSetting(std::string("foldable\\x_position"));
|
ReadDoubleSetting(std::string("foldable\\x_position"));
|
||||||
control_data.foldable_position.second =
|
control_data.foldable_position.second =
|
||||||
ReadDoubleSetting(std::string("foldable\\y_position"));
|
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);
|
AndroidSettings::values.overlay_control_data.push_back(control_data);
|
||||||
}
|
}
|
||||||
EndArray();
|
EndArray();
|
||||||
|
@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() {
|
||||||
control_data.foldable_position.first);
|
control_data.foldable_position.first);
|
||||||
WriteDoubleSetting(std::string("foldable\\y_position"),
|
WriteDoubleSetting(std::string("foldable\\y_position"),
|
||||||
control_data.foldable_position.second);
|
control_data.foldable_position.second);
|
||||||
|
WriteDoubleSetting(std::string("individual_scale"), static_cast<double>(control_data.individual_scale));
|
||||||
}
|
}
|
||||||
EndArray();
|
EndArray();
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace AndroidSettings {
|
||||||
std::pair<double, double> landscape_position;
|
std::pair<double, double> landscape_position;
|
||||||
std::pair<double, double> portrait_position;
|
std::pair<double, double> portrait_position;
|
||||||
std::pair<double, double> foldable_position;
|
std::pair<double, double> foldable_position;
|
||||||
|
float individual_scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Values {
|
struct Values {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN
|
||||||
env->NewObject(Common::Android::GetOverlayControlDataClass(),
|
env->NewObject(Common::Android::GetOverlayControlDataClass(),
|
||||||
Common::Android::GetOverlayControlDataConstructor(),
|
Common::Android::GetOverlayControlDataConstructor(),
|
||||||
Common::Android::ToJString(env, control_data.id), control_data.enabled,
|
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);
|
env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
|
||||||
}
|
}
|
||||||
return joverlayControlDataArray;
|
return joverlayControlDataArray;
|
||||||
|
@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
|
||||||
env,
|
env,
|
||||||
env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField())));
|
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{
|
AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
|
||||||
Common::Android::GetJString(env, jidString), enabled, landscape_position,
|
Common::Android::GetJString(env, jidString), enabled, landscape_position,
|
||||||
portrait_position, foldable_position});
|
portrait_position, foldable_position, individual_scale});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
src/android/app/src/main/res/layout/dialog_overlay_scale.xml
Normal file
74
src/android/app/src/main/res/layout/dialog_overlay_scale.xml
Normal file
|
@ -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>
|
|
@ -850,6 +850,7 @@
|
||||||
<string name="touchscreen">Touchscreen</string>
|
<string name="touchscreen">Touchscreen</string>
|
||||||
<string name="lock_drawer">Lock drawer</string>
|
<string name="lock_drawer">Lock drawer</string>
|
||||||
<string name="unlock_drawer">Unlock drawer</string>
|
<string name="unlock_drawer">Unlock drawer</string>
|
||||||
|
<string name="reset">Reset</string>
|
||||||
|
|
||||||
<string name="load_settings">Loading settings…</string>
|
<string name="load_settings">Loading settings…</string>
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ static jclass s_overlay_control_data_class;
|
||||||
static jmethodID s_overlay_control_data_constructor;
|
static jmethodID s_overlay_control_data_constructor;
|
||||||
static jfieldID s_overlay_control_data_id_field;
|
static jfieldID s_overlay_control_data_id_field;
|
||||||
static jfieldID s_overlay_control_data_enabled_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_landscape_position_field;
|
||||||
static jfieldID s_overlay_control_data_portrait_position_field;
|
static jfieldID s_overlay_control_data_portrait_position_field;
|
||||||
static jfieldID s_overlay_control_data_foldable_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;
|
return s_overlay_control_data_enabled_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jfieldID GetOverlayControlDataIndividualScaleField() {
|
||||||
|
return s_overlay_control_data_individual_scale_field;
|
||||||
|
}
|
||||||
|
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField() {
|
jfieldID GetOverlayControlDataLandscapePositionField() {
|
||||||
return s_overlay_control_data_landscape_position_field;
|
return s_overlay_control_data_landscape_position_field;
|
||||||
}
|
}
|
||||||
|
@ -494,7 +499,7 @@ namespace Common::Android {
|
||||||
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
|
||||||
s_overlay_control_data_constructor =
|
s_overlay_control_data_constructor =
|
||||||
env->GetMethodID(overlay_control_data_class, "<init>",
|
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 =
|
s_overlay_control_data_id_field =
|
||||||
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
|
||||||
s_overlay_control_data_enabled_field =
|
s_overlay_control_data_enabled_field =
|
||||||
|
@ -505,6 +510,8 @@ namespace Common::Android {
|
||||||
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
|
||||||
s_overlay_control_data_foldable_position_field =
|
s_overlay_control_data_foldable_position_field =
|
||||||
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
|
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);
|
env->DeleteLocalRef(overlay_control_data_class);
|
||||||
|
|
||||||
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
|
||||||
|
|
|
@ -67,6 +67,7 @@ jclass GetOverlayControlDataClass();
|
||||||
jmethodID GetOverlayControlDataConstructor();
|
jmethodID GetOverlayControlDataConstructor();
|
||||||
jfieldID GetOverlayControlDataIdField();
|
jfieldID GetOverlayControlDataIdField();
|
||||||
jfieldID GetOverlayControlDataEnabledField();
|
jfieldID GetOverlayControlDataEnabledField();
|
||||||
|
jfieldID GetOverlayControlDataIndividualScaleField();
|
||||||
jfieldID GetOverlayControlDataLandscapePositionField();
|
jfieldID GetOverlayControlDataLandscapePositionField();
|
||||||
jfieldID GetOverlayControlDataPortraitPositionField();
|
jfieldID GetOverlayControlDataPortraitPositionField();
|
||||||
jfieldID GetOverlayControlDataFoldablePositionField();
|
jfieldID GetOverlayControlDataFoldablePositionField();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue