skyline/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt

270 lines
9.2 KiB
Kotlin

/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.input.onscreen
import android.graphics.Canvas
import android.graphics.PointF
import android.graphics.Typeface
import android.os.SystemClock
import androidx.core.graphics.minus
import emu.skyline.R
import emu.skyline.input.ButtonId
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.add
import emu.skyline.utils.multiply
import kotlin.math.roundToInt
open class CircularButton(
onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId,
defaultRelativeX : Float,
defaultRelativeY : Float,
defaultRelativeRadiusToX : Float,
drawableId : Int = R.drawable.ic_button
) : OnScreenButton(
onScreenControllerView,
buttonId,
defaultRelativeX,
defaultRelativeY,
defaultRelativeRadiusToX * 2f,
defaultRelativeRadiusToX * CONFIGURED_ASPECT_RATIO * 2f,
drawableId
) {
val radius get() = itemWidth / 2f
/**
* Checks if [x] and [y] are within circle
*/
override fun isTouched(x : Float, y : Float) : Boolean = (PointF(currentX, currentY) - (PointF(x, y))).length() <= radius
}
class JoystickButton(
onScreenControllerView : OnScreenControllerView,
val stickId : StickId,
defaultRelativeX : Float,
defaultRelativeY : Float,
defaultRelativeRadiusToX : Float
) : CircularButton(
onScreenControllerView,
stickId.button,
defaultRelativeX,
defaultRelativeY,
defaultRelativeRadiusToX,
R.drawable.ic_button
) {
private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick)
var recenterSticks = false
private lateinit var initialTapPosition : PointF
private var fingerDownTime = 0L
private var fingerUpTime = 0L
var shortDoubleTapped = false
private set
init {
innerButton.buttonSymbolPaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
}
override fun renderCenteredText(canvas : Canvas, text : String, size : Float, x : Float, y : Float, alpha : Int) = Unit
override fun render(canvas : Canvas) {
super.render(canvas)
innerButton.width = width
innerButton.height = height
innerButton.render(canvas)
}
override fun onFingerDown(x : Float, y : Float) {
val relativeX = x / width
val relativeY = (y - heightDiff) / adjustedHeight
if (recenterSticks) {
this.relativeX = relativeX
this.relativeY = relativeY
}
innerButton.relativeX = relativeX
innerButton.relativeY = relativeY
// If first and second tap occur within 500 mills, then trigger stick press
val currentTime = SystemClock.elapsedRealtime()
initialTapPosition = PointF(x, y)
val firstTapDiff = fingerUpTime - fingerDownTime
val secondTapDiff = currentTime - fingerUpTime
if (firstTapDiff in 0..500 && secondTapDiff in 0..500) {
shortDoubleTapped = true
// Indicate stick being pressed with lower alpha value
isPressed = true
}
fingerDownTime = currentTime
}
override fun onFingerUp(x : Float, y : Float) {
loadConfigValues()
innerButton.relativeX = relativeX
innerButton.relativeY = relativeY
fingerUpTime = SystemClock.elapsedRealtime()
shortDoubleTapped = false
isPressed = false
}
fun onFingerMoved(x : Float, y : Float, manualMove : Boolean = true) : PointF {
val position = PointF(currentX, currentY)
var finger = PointF(x, y)
val outerToInner = finger.minus(position)
val distance = outerToInner.length()
if (distance > radius) {
// Limit distance to radius
finger = position.add(outerToInner.multiply(1f / distance * radius))
}
// If finger get moved to much, then don't trigger as joystick being pressed
if (manualMove && initialTapPosition.minus(finger).length() > radius * 0.075f) {
fingerDownTime = 0
fingerUpTime = 0
}
innerButton.relativeX = finger.x / width
innerButton.relativeY = (finger.y - heightDiff) / adjustedHeight
return finger.minus(position).multiply(1f / radius)
}
fun outerToInner() = PointF(innerButton.currentX, innerButton.currentY).minus(PointF(currentX, currentY))
fun outerToInnerRelative() = outerToInner().multiply(1f / radius)
override fun edit(x : Float, y : Float) {
super.edit(x, y)
innerButton.relativeX = relativeX
innerButton.relativeY = relativeY
}
override fun resetRelativeValues() {
super.resetRelativeValues()
innerButton.relativeX = relativeX
innerButton.relativeY = relativeY
}
}
open class RectangularButton(
onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId,
defaultRelativeX : Float,
defaultRelativeY : Float,
defaultRelativeWidth : Float,
defaultRelativeHeight : Float,
drawableId : Int = R.drawable.ic_rectangular_button
) : OnScreenButton(
onScreenControllerView,
buttonId,
defaultRelativeX,
defaultRelativeY,
defaultRelativeWidth,
defaultRelativeHeight,
drawableId
) {
override fun isTouched(x : Float, y : Float) = currentBounds.contains(x.roundToInt(), y.roundToInt())
}
class TriggerButton(
onScreenControllerView : OnScreenControllerView,
buttonId : ButtonId,
defaultRelativeX : Float,
defaultRelativeY : Float,
defaultRelativeWidth : Float,
defaultRelativeHeight : Float
) : RectangularButton(
onScreenControllerView,
buttonId,
defaultRelativeX,
defaultRelativeY,
defaultRelativeWidth,
defaultRelativeHeight,
when (buttonId) {
ZL -> R.drawable.ic_trigger_button_left
ZR -> R.drawable.ic_trigger_button_right
else -> error("Unsupported trigger button")
}
)
class Controls(onScreenControllerView : OnScreenControllerView) {
private val buttonA = CircularButton(onScreenControllerView, A, 0.95f, 0.65f, 0.025f)
private val buttonB = CircularButton(onScreenControllerView, B, 0.9f, 0.765f, 0.025f)
private val buttonX = CircularButton(onScreenControllerView, X, 0.9f, 0.535f, 0.025f)
private val buttonY = CircularButton(onScreenControllerView, Y, 0.85f, 0.65f, 0.025f)
private val buttonDpadLeft = CircularButton(onScreenControllerView, DpadLeft, 0.2f, 0.65f, 0.025f)
private val buttonDpadUp = CircularButton(onScreenControllerView, DpadUp, 0.25f, 0.535f, 0.025f)
private val buttonDpadRight = CircularButton(onScreenControllerView, DpadRight, 0.3f, 0.65f, 0.025f)
private val buttonDpadDown = CircularButton(onScreenControllerView, DpadDown, 0.25f, 0.765f, 0.025f)
private val buttonL = RectangularButton(onScreenControllerView, L, 0.1f, 0.25f, 0.09f, 0.1f)
private val buttonR = RectangularButton(onScreenControllerView, R, 0.9f, 0.25f, 0.09f, 0.1f)
private val buttonZL = TriggerButton(onScreenControllerView, ZL, 0.1f, 0.1f, 0.09f, 0.1f)
private val buttonZR = TriggerButton(onScreenControllerView, ZR, 0.9f, 0.1f, 0.09f, 0.1f)
private val circularButtonPairs = listOf(setOf(buttonA, buttonB, buttonX, buttonY), setOf(buttonDpadLeft, buttonDpadUp, buttonDpadRight, buttonDpadDown))
private val triggerButtonPairs = listOf(setOf(buttonL, buttonZL), setOf(buttonR, buttonZR))
val buttonPairs = circularButtonPairs + triggerButtonPairs
val circularButtons = circularButtonPairs.flatten() + listOf(
CircularButton(onScreenControllerView, Plus, 0.57f, 0.75f, 0.025f),
CircularButton(onScreenControllerView, Minus, 0.43f, 0.75f, 0.025f),
CircularButton(onScreenControllerView, Menu, 0.5f, 0.75f, 0.025f)
)
val joysticks = listOf(
JoystickButton(onScreenControllerView, Left, 0.1f, 0.8f, 0.05f),
JoystickButton(onScreenControllerView, Right, 0.75f, 0.6f, 0.05f)
)
val rectangularButtons = listOf(buttonL, buttonR)
val triggerButtons = listOf(buttonZL, buttonZR)
val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons
/**
* We can take any of the global scale variables from the buttons since the value is shared across all buttons
*/
var globalScale
get() = circularButtons.first().config.globalScale
set(value) {
circularButtons.first().config.globalScale = value
}
/**
* We can take any of the alpha variables from the buttons since the value is shared across all buttons
*/
var alpha
get() = circularButtons.first().config.alpha
set(value) {
circularButtons.first().config.alpha = value
}
/**
* We can take any of the global text color variables from the buttons
*/
val globalTextColor
get() = circularButtons.first().config.textColor
/**
* We can take any of the global background color variables from the buttons
*/
val globalBackgroundColor
get() = circularButtons.first().config.backgroundColor
}