[android, inputOverlay] Add snap to grid option and allow editing the overlay without opening a game (#3234)
The new changes are in the input overlay section Known issues: - Auto hide, also hides the overlay in gameless edit mode - Same goes for the controller auto hide option Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3234 Reviewed-by: DraVee <dravee@eden-emu.dev> Reviewed-by: Maufeat <sahyno1996@gmail.com> Co-authored-by: kleidis <kleidis1@protonmail.com> Co-committed-by: kleidis <kleidis1@protonmail.com>
This commit is contained in:
parent
a79eab9564
commit
76be55bc2f
|
|
@ -639,6 +639,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
|||
|
||||
companion object {
|
||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||
const val EXTRA_OVERLAY_GAMELESS_EDIT_MODE = "overlayGamelessEditMode"
|
||||
|
||||
fun stopForegroundService(activity: Activity) {
|
||||
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||
|
|
@ -652,6 +653,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
|||
activity.startActivity(launcher)
|
||||
}
|
||||
|
||||
fun launchForOverlayEdit(context: Context): Intent {
|
||||
return Intent(context, EmulationActivity::class.java).apply {
|
||||
putExtra(EXTRA_OVERLAY_GAMELESS_EDIT_MODE, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
|
||||
if (view == null) {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||
DPAD_SLIDE("dpad_slide"),
|
||||
HAPTIC_FEEDBACK("haptic_feedback"),
|
||||
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
||||
OVERLAY_SNAP_TO_GRID("overlay_snap_to_grid"),
|
||||
TOUCHSCREEN("touchscreen"),
|
||||
AIRPLANE_MODE("airplane_mode"),
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
|
||||
MY_PAGE_APPLET("my_page_applet_mode"),
|
||||
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
||||
OVERLAY_GRID_SIZE("overlay_grid_size"),
|
||||
DEBUG_KNOBS("debug_knobs")
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* A settings item that launches an intent when clicked.
|
||||
*/
|
||||
class LaunchableSetting(
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val launchIntent: (android.content.Context) -> Intent
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = SettingsItem.TYPE_LAUNCHABLE
|
||||
}
|
||||
|
|
@ -97,6 +97,7 @@ abstract class SettingsItem(
|
|||
const val TYPE_INPUT_PROFILE = 10
|
||||
const val TYPE_STRING_INPUT = 11
|
||||
const val TYPE_SPINBOX = 12
|
||||
const val TYPE_LAUNCHABLE = 13
|
||||
|
||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||
|
||||
|
|
@ -364,6 +365,30 @@ abstract class SettingsItem(
|
|||
warningMessage = R.string.warning_resolution
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY,
|
||||
titleId = R.string.show_input_overlay,
|
||||
descriptionId = R.string.show_input_overlay_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID,
|
||||
titleId = R.string.overlay_snap_to_grid,
|
||||
descriptionId = R.string.overlay_snap_to_grid_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.OVERLAY_GRID_SIZE,
|
||||
titleId = R.string.overlay_grid_size,
|
||||
descriptionId = R.string.overlay_grid_size_description,
|
||||
min = 16,
|
||||
max = 128,
|
||||
units = "px"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ class SettingsAdapter(
|
|||
StringInputViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_LAUNCHABLE -> {
|
||||
LaunchableViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
else -> {
|
||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||
}
|
||||
|
|
@ -209,6 +212,11 @@ class SettingsAdapter(
|
|||
fragment.view?.findNavController()?.navigate(action)
|
||||
}
|
||||
|
||||
fun onLaunchableClick(item: LaunchableSetting) {
|
||||
val intent = item.launchIntent(context)
|
||||
fragment.requireActivity().startActivity(intent)
|
||||
}
|
||||
|
||||
fun onInputProfileClick(item: InputProfileSetting, position: Int) {
|
||||
InputProfileDialogFragment.newInstance(
|
||||
settingsViewModel,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.preference.PreferenceManager
|
|||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
||||
|
|
@ -294,6 +295,19 @@ class SettingsFragmentPresenter(
|
|||
|
||||
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(BooleanSetting.SHOW_INPUT_OVERLAY.key)
|
||||
add(BooleanSetting.OVERLAY_SNAP_TO_GRID.key)
|
||||
add(IntSetting.OVERLAY_GRID_SIZE.key)
|
||||
add(
|
||||
LaunchableSetting(
|
||||
titleId = R.string.edit_overlay_layout,
|
||||
descriptionId = R.string.edit_overlay_layout_description,
|
||||
launchIntent = { context ->
|
||||
EmulationActivity.launchForOverlayEdit(context)
|
||||
}
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.input_overlay_behavior))
|
||||
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.LaunchableSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class LaunchableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: LaunchableSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as LaunchableSetting
|
||||
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.text = ""
|
||||
binding.textSettingValue.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
0, 0, R.drawable.ic_arrow_forward, 0
|
||||
)
|
||||
|
||||
binding.buttonClear.setVisible(false)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
adapter.onLaunchableClick(setting)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
// no-op
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -201,6 +201,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
super.onCreate(savedInstanceState)
|
||||
updateOrientation()
|
||||
|
||||
if (args.overlayGamelessEditMode) {
|
||||
return
|
||||
}
|
||||
|
||||
val intent = requireActivity().intent
|
||||
val intentUri: Uri? = intent.data
|
||||
intentGame = null
|
||||
|
|
@ -558,6 +562,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
return
|
||||
}
|
||||
|
||||
if (args.overlayGamelessEditMode) {
|
||||
setupOverlayGamelessEditMode()
|
||||
return
|
||||
}
|
||||
|
||||
if (game == null) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Game not yet initialized in onViewCreated - will be set up by async intent handler"
|
||||
|
|
@ -568,6 +577,39 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
completeViewSetup()
|
||||
}
|
||||
|
||||
|
||||
private fun setupOverlayGamelessEditMode() {
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.refreshControls(gameless = true)
|
||||
}
|
||||
|
||||
binding.doneControlConfig.setOnClickListener {
|
||||
finishOverlayGamelessEditMode()
|
||||
}
|
||||
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
binding.loadingIndicator.visibility = View.GONE
|
||||
|
||||
// in gameless edit mode, back = done
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
viewLifecycleOwner,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finishOverlayGamelessEditMode()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun finishOverlayGamelessEditMode() {
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
NativeConfig.saveGlobalConfig()
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
private fun completeViewSetup() {
|
||||
if (_binding == null || game == null) {
|
||||
return
|
||||
|
|
@ -900,9 +942,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
b.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
||||
}
|
||||
} else {
|
||||
b.surfaceInputOverlay.setVisible(
|
||||
val shouldShowOverlay = if (args.overlayGamelessEditMode) {
|
||||
true
|
||||
} else {
|
||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
||||
)
|
||||
}
|
||||
b.surfaceInputOverlay.setVisible(shouldShowOverlay)
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
b.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
|
|
@ -1531,6 +1576,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
|
||||
findItem(R.id.menu_show_overlay).isChecked =
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
findItem(R.id.menu_snap_to_grid).isChecked =
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()
|
||||
findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
|
||||
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
|
||||
}
|
||||
|
|
@ -1559,6 +1606,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
true
|
||||
}
|
||||
|
||||
R.id.menu_snap_to_grid -> {
|
||||
it.isChecked = !it.isChecked
|
||||
BooleanSetting.OVERLAY_SNAP_TO_GRID.setBoolean(it.isChecked)
|
||||
binding.surfaceInputOverlay.invalidate()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_adjust_overlay -> {
|
||||
adjustOverlay()
|
||||
true
|
||||
|
|
@ -1942,6 +1996,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
fun handleScreenTap(isLongTap: Boolean) {
|
||||
if (binding.surfaceInputOverlay.isGamelessMode()) {
|
||||
return
|
||||
}
|
||||
|
||||
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
|
||||
val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
|
||||
|
||||
|
|
@ -1963,6 +2021,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
private fun initializeOverlayAutoHide() {
|
||||
if (binding.surfaceInputOverlay.isGamelessMode()) {
|
||||
return
|
||||
}
|
||||
|
||||
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
|
||||
val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
|
||||
val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
|
|
@ -50,6 +52,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
|
||||
|
||||
private var inEditMode = false
|
||||
private var gamelessMode = false
|
||||
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||
|
|
@ -60,6 +63,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
private var hasMoved = false
|
||||
private val moveThreshold = 20f
|
||||
|
||||
private val gridPaint = Paint().apply {
|
||||
color = Color.argb(60, 255, 255, 255)
|
||||
strokeWidth = 1f
|
||||
style = Paint.Style.STROKE
|
||||
}
|
||||
|
||||
private lateinit var windowInsets: WindowInsets
|
||||
|
||||
var layout = OverlayLayout.Landscape
|
||||
|
|
@ -91,6 +100,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
|
||||
override fun draw(canvas: Canvas) {
|
||||
super.draw(canvas)
|
||||
|
||||
// Draw grid when in edit mode and snap-to-grid is enabled
|
||||
if (inEditMode && BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
drawGrid(canvas)
|
||||
}
|
||||
|
||||
for (button in overlayButtons) {
|
||||
button.draw(canvas)
|
||||
}
|
||||
|
|
@ -102,6 +117,26 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
}
|
||||
}
|
||||
|
||||
private fun drawGrid(canvas: Canvas) {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
val width = canvas.width
|
||||
val height = canvas.height
|
||||
|
||||
// Draw vertical lines
|
||||
var x = 0
|
||||
while (x <= width) {
|
||||
canvas.drawLine(x.toFloat(), 0f, x.toFloat(), height.toFloat(), gridPaint)
|
||||
x += gridSize
|
||||
}
|
||||
|
||||
// Draw horizontal lines
|
||||
var y = 0
|
||||
while (y <= height) {
|
||||
canvas.drawLine(0f, y.toFloat(), width.toFloat(), y.toFloat(), gridPaint)
|
||||
y += gridSize
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
if (inEditMode) {
|
||||
return onTouchWhileEditing(event)
|
||||
|
|
@ -668,14 +703,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
}
|
||||
}
|
||||
|
||||
fun refreshControls() {
|
||||
fun refreshControls(gameless: Boolean = false) {
|
||||
// Store gameless mode if set to true
|
||||
if (gameless) {
|
||||
gamelessMode = true
|
||||
}
|
||||
|
||||
// Remove all the overlay buttons from the HashSet.
|
||||
overlayButtons.clear()
|
||||
overlayDpads.clear()
|
||||
overlayJoysticks.clear()
|
||||
|
||||
// Add all the enabled overlay items back to the HashSet.
|
||||
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
|
||||
if (gamelessMode || BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
|
||||
addOverlayControls(layout)
|
||||
}
|
||||
invalidate()
|
||||
|
|
@ -712,9 +752,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
if (!editMode) {
|
||||
scaleDialog?.dismiss()
|
||||
scaleDialog = null
|
||||
gamelessMode = false
|
||||
}
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun isGamelessMode(): Boolean = gamelessMode
|
||||
|
||||
private fun showScaleDialog(
|
||||
button: InputOverlayDrawableButton?,
|
||||
dpad: InputOverlayDrawableDpad?,
|
||||
|
|
@ -867,6 +912,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Increase this number every time there is a breaking change to every overlay layout
|
||||
const val OVERLAY_VERSION = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -11,6 +14,8 @@ import android.graphics.drawable.BitmapDrawable
|
|||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||
|
||||
/**
|
||||
|
|
@ -121,11 +126,23 @@ class InputOverlayDrawableButton(
|
|||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
setBounds(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
width + controlPositionX,
|
||||
height + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
width + finalX,
|
||||
height + finalY
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
previousTouchY = fingerPositionY
|
||||
|
|
@ -134,6 +151,11 @@ class InputOverlayDrawableButton(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
defaultStateBitmap.setBounds(left, top, right, bottom)
|
||||
pressedStateBitmap.setBounds(left, top, right, bottom)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import android.graphics.drawable.BitmapDrawable
|
|||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
|
|
@ -229,11 +231,23 @@ class InputOverlayDrawableDpad(
|
|||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
setBounds(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
width + controlPositionX,
|
||||
height + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
width + finalX,
|
||||
height + finalY
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
previousTouchY = fingerPositionY
|
||||
|
|
@ -242,6 +256,11 @@ class InputOverlayDrawableDpad(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
fun setPosition(x: Int, y: Int) {
|
||||
controlPositionX = x
|
||||
controlPositionY = y
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
|||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
|
|
@ -213,25 +214,37 @@ class InputOverlayDrawableJoystick(
|
|||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
|
||||
val finalX = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionX)
|
||||
} else {
|
||||
controlPositionX
|
||||
}
|
||||
val finalY = if (BooleanSetting.OVERLAY_SNAP_TO_GRID.getBoolean()) {
|
||||
snapToGrid(controlPositionY)
|
||||
} else {
|
||||
controlPositionY
|
||||
}
|
||||
|
||||
bounds = Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
virtBounds = Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
setInnerBounds()
|
||||
bounds = Rect(
|
||||
Rect(
|
||||
controlPositionX,
|
||||
controlPositionY,
|
||||
outerBitmap.intrinsicWidth + controlPositionX,
|
||||
outerBitmap.intrinsicHeight + controlPositionY
|
||||
finalX,
|
||||
finalY,
|
||||
outerBitmap.intrinsicWidth + finalX,
|
||||
outerBitmap.intrinsicHeight + finalY
|
||||
)
|
||||
)
|
||||
previousTouchX = fingerPositionX
|
||||
|
|
@ -242,6 +255,11 @@ class InputOverlayDrawableJoystick(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun snapToGrid(value: Int): Int {
|
||||
val gridSize = IntSetting.OVERLAY_GRID_SIZE.getInt()
|
||||
return ((value + gridSize / 2) / gridSize) * gridSize
|
||||
}
|
||||
|
||||
private fun setInnerBounds() {
|
||||
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
|
||||
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
|
||||
|
|
|
|||
|
|
@ -146,6 +146,10 @@ namespace AndroidSettings {
|
|||
|
||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<s32> overlay_grid_size{linkage, 32, "overlay_grid_size",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen",
|
||||
Settings::Category::Overlay};
|
||||
Settings::Setting<s32> lock_drawer{linkage, false, "lock_drawer",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@
|
|||
android:id="@+id/menu_edit_overlay"
|
||||
android:title="@string/emulation_touch_overlay_edit" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_snap_to_grid"
|
||||
android:title="@string/emulation_snap_to_grid"
|
||||
android:checkable="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_adjust_overlay"
|
||||
android:title="@string/emulation_control_adjust" />
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@
|
|||
android:name="custom"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="overlayGamelessEditMode"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
|
||||
<activity
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@
|
|||
|
||||
|
||||
<!-- Input Overlay -->
|
||||
<string name="show_input_overlay">Show Input Overlay</string>
|
||||
<string name="show_input_overlay_description">Display touch controls overlay during emulation</string>
|
||||
<string name="overlay_snap_to_grid">Snap to Grid</string>
|
||||
<string name="overlay_snap_to_grid_description">Snap overlay controls to a grid when editing</string>
|
||||
<string name="overlay_grid_size">Grid Size</string>
|
||||
<string name="overlay_grid_size_description">Size of the grid cells in pixels</string>
|
||||
<string name="input_overlay_behavior">Behavior</string>
|
||||
<string name="overlay_auto_hide">Overlay Auto Hide</string>
|
||||
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
|
||||
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
|
||||
|
|
@ -31,6 +38,8 @@
|
|||
|
||||
<string name="input_overlay_options">Input Overlay</string>
|
||||
<string name="input_overlay_options_description">Configure on-screen controls</string>
|
||||
<string name="edit_overlay_layout">Edit Overlay Layout</string>
|
||||
<string name="edit_overlay_layout_description">Adjust the position and scale of on-screen controls</string>
|
||||
|
||||
|
||||
<!-- Stats Overlay settings -->
|
||||
|
|
@ -841,6 +850,7 @@
|
|||
<string name="emulation_control_opacity">Opacity</string>
|
||||
<string name="emulation_touch_overlay_reset">Reset overlay</string>
|
||||
<string name="emulation_touch_overlay_edit">Edit overlay</string>
|
||||
<string name="emulation_snap_to_grid">Snap to grid</string>
|
||||
<string name="emulation_pause">Pause emulation</string>
|
||||
<string name="emulation_unpause">Unpause emulation</string>
|
||||
<string name="emulation_input_overlay">Overlay options</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue