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