mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 15:09:17 +01:00
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.
This commit is contained in:
parent
32f995165c
commit
d86b2ec3e9
@ -337,6 +337,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
|
|||||||
setOnStickStateChangedListener(::onStickStateChanged)
|
setOnStickStateChangedListener(::onStickStateChanged)
|
||||||
hapticFeedback = appSettings.onScreenControl && appSettings.onScreenControlFeedback
|
hapticFeedback = appSettings.onScreenControl && appSettings.onScreenControlFeedback
|
||||||
recenterSticks = appSettings.onScreenControlRecenterSticks
|
recenterSticks = appSettings.onScreenControlRecenterSticks
|
||||||
|
stickRegions = appSettings.onScreenControlUseStickRegions
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.onScreenControllerToggle.apply {
|
binding.onScreenControllerToggle.apply {
|
||||||
|
@ -100,6 +100,10 @@ class ControllerActivity : AppCompatActivity() {
|
|||||||
appSettings.onScreenControlRecenterSticks = item.checked
|
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 = {
|
items.add(ControllerViewItem(content = getString(R.string.osc_edit), onClick = {
|
||||||
startActivity(Intent(this, OnScreenEditActivity::class.java))
|
startActivity(Intent(this, OnScreenEditActivity::class.java))
|
||||||
}))
|
}))
|
||||||
|
@ -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)!!
|
protected val drawable = ContextCompat.getDrawable(onScreenControllerView.context, drawableId)!!
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ abstract class OnScreenButton(
|
|||||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
private val textBoundsRect = Rect()
|
protected val textBoundsRect = Rect()
|
||||||
|
|
||||||
var relativeX = config.relativeX
|
var relativeX = config.relativeX
|
||||||
var relativeY = config.relativeY
|
var relativeY = config.relativeY
|
||||||
|
@ -65,10 +65,16 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
field = value
|
field = value
|
||||||
controls.joysticks.forEach { it.recenterSticks = recenterSticks }
|
controls.joysticks.forEach { it.recenterSticks = recenterSticks }
|
||||||
}
|
}
|
||||||
|
var stickRegions = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
controls.setStickRegions(value)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
var hapticFeedback = false
|
var hapticFeedback = false
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback }
|
controls.buttons.forEach { it.hapticFeedback = hapticFeedback }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val editInfo = OnScreenEditInfo()
|
internal val editInfo = OnScreenEditInfo()
|
||||||
@ -116,7 +122,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
val x by lazy { event.getX(actionIndex) }
|
val x by lazy { event.getX(actionIndex) }
|
||||||
val y by lazy { event.getY(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) {
|
when (event.action and event.actionMasked) {
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_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) {
|
when (event.actionMasked) {
|
||||||
MotionEvent.ACTION_UP,
|
MotionEvent.ACTION_UP,
|
||||||
MotionEvent.ACTION_POINTER_UP,
|
MotionEvent.ACTION_POINTER_UP,
|
||||||
|
@ -100,6 +100,7 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
||||||
|
|
||||||
binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks
|
binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks
|
||||||
|
binding.onScreenControllerView.stickRegions = appSettings.onScreenControlUseStickRegions
|
||||||
|
|
||||||
val snapToGrid = appSettings.onScreenControlSnapToGrid
|
val snapToGrid = appSettings.onScreenControlSnapToGrid
|
||||||
binding.onScreenControllerView.setSnapToGrid(snapToGrid)
|
binding.onScreenControllerView.setSnapToGrid(snapToGrid)
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
package emu.skyline.input.onscreen
|
package emu.skyline.input.onscreen
|
||||||
|
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import androidx.core.graphics.minus
|
import androidx.core.graphics.minus
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
@ -15,6 +19,7 @@ import emu.skyline.input.ButtonId.*
|
|||||||
import emu.skyline.input.StickId
|
import emu.skyline.input.StickId
|
||||||
import emu.skyline.input.StickId.Left
|
import emu.skyline.input.StickId.Left
|
||||||
import emu.skyline.input.StickId.Right
|
import emu.skyline.input.StickId.Right
|
||||||
|
import emu.skyline.utils.SwitchColors
|
||||||
import emu.skyline.utils.add
|
import emu.skyline.utils.add
|
||||||
import emu.skyline.utils.multiply
|
import emu.skyline.utils.multiply
|
||||||
import kotlin.math.roundToInt
|
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
|
override fun isTouched(x : Float, y : Float) : Boolean = (PointF(currentX, currentY) - (PointF(x, y))).length() <= radius
|
||||||
}
|
}
|
||||||
|
|
||||||
class JoystickButton(
|
open class JoystickButton(
|
||||||
onScreenControllerView : OnScreenControllerView,
|
onScreenControllerView : OnScreenControllerView,
|
||||||
val stickId : StickId,
|
val stickId : StickId,
|
||||||
defaultRelativeX : Float,
|
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)
|
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 initialTapPosition = PointF()
|
||||||
private var fingerDownTime = 0L
|
private var fingerDownTime = 0L
|
||||||
private var fingerUpTime = 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(
|
open class RectangularButton(
|
||||||
onScreenControllerView : OnScreenControllerView,
|
onScreenControllerView : OnScreenControllerView,
|
||||||
buttonId : ButtonId,
|
buttonId : ButtonId,
|
||||||
@ -236,14 +320,41 @@ class Controls(onScreenControllerView : OnScreenControllerView) {
|
|||||||
CircularButton(onScreenControllerView, Menu, 0.5f, 0.85f, 0.029f)
|
CircularButton(onScreenControllerView, Menu, 0.5f, 0.85f, 0.029f)
|
||||||
)
|
)
|
||||||
|
|
||||||
val joysticks = listOf(
|
private val joystickRegions = listOf<JoystickButton>(
|
||||||
|
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, Left, 0.24f, 0.75f, 0.06f),
|
||||||
JoystickButton(onScreenControllerView, Right, 0.9f, 0.53f, 0.06f)
|
JoystickButton(onScreenControllerView, Right, 0.9f, 0.53f, 0.06f)
|
||||||
|
)
|
||||||
|
|
||||||
|
var joysticks = joystickButtons
|
||||||
|
private set
|
||||||
|
|
||||||
val rectangularButtons = listOf(buttonL, buttonR)
|
val rectangularButtons = listOf(buttonL, buttonR)
|
||||||
|
|
||||||
val triggerButtons = listOf(buttonZL, buttonZR)
|
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
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ class AppSettings @Inject constructor(@ApplicationContext private val context :
|
|||||||
var onScreenControlFeedback by sharedPreferences(context, true)
|
var onScreenControlFeedback by sharedPreferences(context, true)
|
||||||
var onScreenControlRecenterSticks by sharedPreferences(context, true)
|
var onScreenControlRecenterSticks by sharedPreferences(context, true)
|
||||||
var onScreenControlSnapToGrid by sharedPreferences(context, false)
|
var onScreenControlSnapToGrid by sharedPreferences(context, false)
|
||||||
|
var onScreenControlUseStickRegions by sharedPreferences(context, false)
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
var refreshRequired by sharedPreferences(context, false)
|
var refreshRequired by sharedPreferences(context, false)
|
||||||
|
@ -176,6 +176,8 @@
|
|||||||
<string name="confirm">Confirm</string>
|
<string name="confirm">Confirm</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="osc_recenter_sticks">Recenter Sticks On Touch</string>
|
<string name="osc_recenter_sticks">Recenter Sticks On Touch</string>
|
||||||
|
<string name="osc_use_stick_regions">Use Stick Regions</string>
|
||||||
|
<string name="osc_use_stick_regions_desc">When enabled, the stick activaation radius is extended to the corresponding half of the screen.</string>
|
||||||
<string name="controller">Controller</string>
|
<string name="controller">Controller</string>
|
||||||
<string name="config_controller">Configure Controller</string>
|
<string name="config_controller">Configure Controller</string>
|
||||||
<string name="controller_type">Controller Type</string>
|
<string name="controller_type">Controller Type</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user