From d86b2ec3e933a80c2a5b9f02f2a49974c8b00b76 Mon Sep 17 00:00:00 2001 From: lynxnb Date: Tue, 18 Apr 2023 22:17:37 +0200 Subject: [PATCH] Implement OSC stick regions Stick regions extend the activation area of the sticks to rectangles covering the corresponding half of the screen. E.g. for the left stick: when any point of the left side of the screen is touched, the stick is repositioned there, and acts as if it was centered in the touched position. When the finger is lifter, the stick is hidden. --- .../java/emu/skyline/EmulationActivity.kt | 1 + .../emu/skyline/input/ControllerActivity.kt | 4 + .../skyline/input/onscreen/OnScreenButton.kt | 4 +- .../input/onscreen/OnScreenControllerView.kt | 17 ++- .../input/onscreen/OnScreenEditActivity.kt | 1 + .../input/onscreen/OnScreenItemDefinitions.kt | 119 +++++++++++++++++- .../java/emu/skyline/settings/AppSettings.kt | 1 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 140 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index fb8b2f47..41a42742 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -337,6 +337,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo setOnStickStateChangedListener(::onStickStateChanged) hapticFeedback = appSettings.onScreenControl && appSettings.onScreenControlFeedback recenterSticks = appSettings.onScreenControlRecenterSticks + stickRegions = appSettings.onScreenControlUseStickRegions } binding.onScreenControllerToggle.apply { diff --git a/app/src/main/java/emu/skyline/input/ControllerActivity.kt b/app/src/main/java/emu/skyline/input/ControllerActivity.kt index 0a10957c..a1a25963 100644 --- a/app/src/main/java/emu/skyline/input/ControllerActivity.kt +++ b/app/src/main/java/emu/skyline/input/ControllerActivity.kt @@ -100,6 +100,10 @@ class ControllerActivity : AppCompatActivity() { appSettings.onScreenControlRecenterSticks = item.checked }) + items.add(ControllerCheckBoxViewItem(getString(R.string.osc_use_stick_regions), getString(R.string.osc_use_stick_regions_desc), appSettings.onScreenControlUseStickRegions) { item, position -> + appSettings.onScreenControlUseStickRegions = item.checked + }) + items.add(ControllerViewItem(content = getString(R.string.osc_edit), onClick = { startActivity(Intent(this, OnScreenEditActivity::class.java)) })) diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt index 1f36aa35..fca2162f 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt @@ -60,7 +60,7 @@ abstract class OnScreenButton( } } - final override val config = OnScreenConfigurationImpl(onScreenControllerView.context, buttonId, defaultRelativeX, defaultRelativeY, defaultEnabled) + final override val config : OnScreenConfiguration = OnScreenConfigurationImpl(onScreenControllerView.context, buttonId, defaultRelativeX, defaultRelativeY, defaultEnabled) protected val drawable = ContextCompat.getDrawable(onScreenControllerView.context, drawableId)!! @@ -69,7 +69,7 @@ abstract class OnScreenButton( typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) isAntiAlias = true } - private val textBoundsRect = Rect() + protected val textBoundsRect = Rect() var relativeX = config.relativeX var relativeY = config.relativeY diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt index c64e462d..e818abea 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt @@ -65,10 +65,16 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs field = value controls.joysticks.forEach { it.recenterSticks = recenterSticks } } + var stickRegions = false + set(value) { + field = value + controls.setStickRegions(value) + invalidate() + } var hapticFeedback = false set(value) { field = value - (controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback } + controls.buttons.forEach { it.hapticFeedback = hapticFeedback } } internal val editInfo = OnScreenEditInfo() @@ -116,7 +122,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs val x by lazy { event.getX(actionIndex) } val y by lazy { event.getY(actionIndex) } - (controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { button -> + controls.buttons.forEach { button -> when (event.action and event.actionMasked) { MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { @@ -174,7 +180,12 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs } } - for (joystick in controls.joysticks) { + if (handled) { + invalidate() + return@OnTouchListener true + } + + controls.joysticks.forEach { joystick -> when (event.actionMasked) { MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt index 94ad9fee..e0a27f45 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt @@ -100,6 +100,7 @@ class OnScreenEditActivity : AppCompatActivity() { getSystemService(VIBRATOR_SERVICE) as Vibrator binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks + binding.onScreenControllerView.stickRegions = appSettings.onScreenControlUseStickRegions val snapToGrid = appSettings.onScreenControlSnapToGrid binding.onScreenControllerView.setSnapToGrid(snapToGrid) diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt index 37d7fdb2..e8c41e85 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt @@ -6,7 +6,11 @@ package emu.skyline.input.onscreen import android.graphics.Canvas +import android.graphics.Paint import android.graphics.PointF +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Typeface import android.os.SystemClock import androidx.core.graphics.minus import emu.skyline.R @@ -15,6 +19,7 @@ import emu.skyline.input.ButtonId.* import emu.skyline.input.StickId import emu.skyline.input.StickId.Left import emu.skyline.input.StickId.Right +import emu.skyline.utils.SwitchColors import emu.skyline.utils.add import emu.skyline.utils.multiply import kotlin.math.roundToInt @@ -45,7 +50,7 @@ open class CircularButton( override fun isTouched(x : Float, y : Float) : Boolean = (PointF(currentX, currentY) - (PointF(x, y))).length() <= radius } -class JoystickButton( +open class JoystickButton( onScreenControllerView : OnScreenControllerView, val stickId : StickId, defaultRelativeX : Float, @@ -61,7 +66,7 @@ class JoystickButton( ) { private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick) - var recenterSticks = false + open var recenterSticks = false private var initialTapPosition = PointF() private var fingerDownTime = 0L private var fingerUpTime = 0L @@ -157,6 +162,85 @@ class JoystickButton( } } +class JoystickRegion( + onScreenControllerView : OnScreenControllerView, + stickId : StickId, + defaultRelativeX : Float, + defaultRelativeY : Float, + defaultRelativeRadiusToX : Float, + private val relativeRegionPosition : RectF, + regionColor : Int +) : JoystickButton( + onScreenControllerView, + stickId, + defaultRelativeX, + defaultRelativeY, + defaultRelativeRadiusToX +) { + /** + * A stick region always re-centers the stick, it always positions the stick at the initial touch position + */ + override var recenterSticks = true + set(_) = Unit + + private val left get() = relativeRegionPosition.left * width + private val top get() = relativeRegionPosition.top * height + private val pixelWidth get() = relativeRegionPosition.width() * width + private val pixelHeight get() = relativeRegionPosition.height() * height + + private val regionBounds get() = Rect(left.toInt(), top.toInt(), (left + pixelWidth).toInt(), (top + pixelHeight).toInt()) + + override fun isTouched(x : Float, y : Float) = regionBounds.contains(x.roundToInt(), y.roundToInt()) + + private val regionPaint = Paint().apply { + this.color = regionColor + alpha = 40 + style = Paint.Style.FILL + } + private val buttonSymbolPaint = Paint().apply { + this.color = SwitchColors.WHITE.color + this.alpha = 40 + typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + textAlign = Paint.Align.LEFT + } + + /** + * Tracks whether the finger is currently down on the stick region. + * The Stick is only rendered when the finger is down. + */ + private var isFingerDown = false + + override fun render(canvas : Canvas) { + if (isFingerDown || editInfo.isEditing) + super.render(canvas) + + // Only draw the stick region area when in edit mode + if (!editInfo.isEditing) + return + + canvas.drawRect(regionBounds, regionPaint) + + val text = stickId.button.short!! + val x = left + pixelWidth / 2f + val y = top + pixelHeight / 2f + buttonSymbolPaint.apply { + textSize = pixelWidth.coerceAtMost(pixelHeight) * 0.4f + getTextBounds(text, 0, text.length, textBoundsRect) + } + canvas.drawText(text, x - textBoundsRect.width() / 2f - textBoundsRect.left, y + textBoundsRect.height() / 2f - textBoundsRect.bottom, buttonSymbolPaint) + } + + override fun onFingerDown(x : Float, y : Float) : Boolean { + isFingerDown = true + return super.onFingerDown(x, y) + } + + override fun onFingerUp(x : Float, y : Float) : Boolean { + isFingerDown = false + return super.onFingerUp(x, y) + } +} + open class RectangularButton( onScreenControllerView : OnScreenControllerView, buttonId : ButtonId, @@ -236,14 +320,41 @@ class Controls(onScreenControllerView : OnScreenControllerView) { CircularButton(onScreenControllerView, Menu, 0.5f, 0.85f, 0.029f) ) - val joysticks = listOf( + private val joystickRegions = listOf( + JoystickRegion( + onScreenControllerView, Left, 0.24f, 0.75f, 0.06f, + RectF(0f, 0f, 0.5f, 1f), SwitchColors.NEON_BLUE.color + ), + JoystickRegion( + onScreenControllerView, Right, 0.9f, 0.53f, 0.06f, + RectF(0.5f, 0f, 1f, 1f), SwitchColors.NEON_RED.color + ), ) + private val joystickButtons = listOf( JoystickButton(onScreenControllerView, Left, 0.24f, 0.75f, 0.06f), JoystickButton(onScreenControllerView, Right, 0.9f, 0.53f, 0.06f) + ) + + var joysticks = joystickButtons + private set + val rectangularButtons = listOf(buttonL, buttonR) val triggerButtons = listOf(buttonZL, buttonZR) - val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons + /** + * All buttons except the joysticks + */ + val buttons = circularButtons + rectangularButtons + triggerButtons + + var allButtons = getCurrentButtons() + private set + + fun setStickRegions(enabled : Boolean) { + joysticks = if (enabled) joystickRegions else joystickButtons + allButtons = getCurrentButtons() + } + + private fun getCurrentButtons() = buttons + joysticks } diff --git a/app/src/main/java/emu/skyline/settings/AppSettings.kt b/app/src/main/java/emu/skyline/settings/AppSettings.kt index f9226460..ffe96971 100644 --- a/app/src/main/java/emu/skyline/settings/AppSettings.kt +++ b/app/src/main/java/emu/skyline/settings/AppSettings.kt @@ -31,6 +31,7 @@ class AppSettings @Inject constructor(@ApplicationContext private val context : var onScreenControlFeedback by sharedPreferences(context, true) var onScreenControlRecenterSticks by sharedPreferences(context, true) var onScreenControlSnapToGrid by sharedPreferences(context, false) + var onScreenControlUseStickRegions by sharedPreferences(context, false) // Other var refreshRequired by sharedPreferences(context, false) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3fca09ab..d5f6b97d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -176,6 +176,8 @@ Confirm Cancel Recenter Sticks On Touch + Use Stick Regions + When enabled, the stick activaation radius is extended to the corresponding half of the screen. Controller Configure Controller Controller Type