mirror of
https://git.sr.ht/~thestr4ng3r/chiaki
synced 2025-08-21 05:53:12 -07:00
Trigger Touchpad Button from TouchpadView on Android
This commit is contained in:
parent
baa034fa3f
commit
bae081d5b3
9 changed files with 105 additions and 67 deletions
|
@ -68,7 +68,6 @@ class DefaultTouchControlsFragment : TouchControlsFragment()
|
|||
binding.optionsButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_OPTIONS)
|
||||
binding.shareButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_SHARE)
|
||||
binding.psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS)
|
||||
binding.touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD)
|
||||
|
||||
binding.l2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { l2State = if(it) 255U else 0U } }
|
||||
binding.r2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { r2State = if(it) 255U else 0U } }
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.view.ViewGroup
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import com.metallic.chiaki.databinding.FragmentTouchpadOnlyBinding
|
||||
import com.metallic.chiaki.lib.ControllerState
|
||||
import io.reactivex.rxkotlin.Observables.combineLatest
|
||||
|
||||
class TouchpadOnlyFragment : TouchControlsFragment()
|
||||
|
@ -31,9 +30,6 @@ class TouchpadOnlyFragment : TouchControlsFragment()
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
|
||||
{
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD)
|
||||
|
||||
touchpadOnlyEnabled?.observe(viewLifecycleOwner, Observer {
|
||||
view.visibility = if(it) View.VISIBLE else View.GONE
|
||||
})
|
||||
|
|
|
@ -13,24 +13,69 @@ import com.metallic.chiaki.lib.ControllerState
|
|||
import io.reactivex.Observable
|
||||
import io.reactivex.subjects.BehaviorSubject
|
||||
import io.reactivex.subjects.Subject
|
||||
import kotlin.math.max
|
||||
|
||||
class TouchpadView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr)
|
||||
{
|
||||
companion object
|
||||
{
|
||||
private const val BUTTON_PRESS_MAX_MOVE_DIST_DP = 32.0f
|
||||
private const val SHORT_BUTTON_PRESS_DURATION_MS = 200L
|
||||
private const val BUTTON_HOLD_DELAY_MS = 500L
|
||||
}
|
||||
|
||||
private val drawableIdle: Drawable?
|
||||
private val drawablePressed: Drawable?
|
||||
|
||||
private val state: ControllerState = ControllerState()
|
||||
private val pointerTouchIds = mutableMapOf<Int, UByte>()
|
||||
|
||||
inner class Touch(
|
||||
val stateId: UByte,
|
||||
private val startX: Float,
|
||||
private val startY: Float)
|
||||
{
|
||||
var lifted = false // will be true but touch still in list when only relevant for short touch
|
||||
private var maxDist: Float = 0.0f
|
||||
val moveInsignificant: Boolean get() = maxDist < BUTTON_PRESS_MAX_MOVE_DIST_DP
|
||||
|
||||
fun onMove(x: Float, y: Float)
|
||||
{
|
||||
val d = (Vector(x, y) - Vector(startX, startY)).length / resources.displayMetrics.density
|
||||
maxDist = max(d, maxDist)
|
||||
}
|
||||
|
||||
val startButtonHoldRunnable = Runnable {
|
||||
if(!moveInsignificant || buttonHeld)
|
||||
return@Runnable
|
||||
state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
|
||||
buttonHeld = true
|
||||
}
|
||||
}
|
||||
private val pointerTouches = mutableMapOf<Int, Touch>()
|
||||
|
||||
private val stateSubject: Subject<ControllerState>
|
||||
= BehaviorSubject.create<ControllerState>().also { it.onNext(state) }
|
||||
val controllerState: Observable<ControllerState> get() = stateSubject
|
||||
|
||||
private val drawable: Drawable?
|
||||
private var shortPressingTouches = listOf<Touch>()
|
||||
private val shortButtonPressLiftRunnable = Runnable {
|
||||
state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
|
||||
shortPressingTouches.forEach {
|
||||
state.stopTouch(it.stateId)
|
||||
}
|
||||
shortPressingTouches = listOf()
|
||||
triggerStateChanged()
|
||||
}
|
||||
|
||||
private var buttonHeld = false
|
||||
|
||||
init
|
||||
{
|
||||
context.theme.obtainStyledAttributes(attrs, R.styleable.TouchpadView, 0, 0).apply {
|
||||
drawable = getDrawable(R.styleable.TouchpadView_drawable)
|
||||
drawableIdle = getDrawable(R.styleable.TouchpadView_drawableIdle)
|
||||
drawablePressed = getDrawable(R.styleable.TouchpadView_drawablePressed)
|
||||
recycle()
|
||||
}
|
||||
isClickable = true
|
||||
|
@ -39,8 +84,9 @@ class TouchpadView @JvmOverloads constructor(
|
|||
override fun onDraw(canvas: Canvas)
|
||||
{
|
||||
super.onDraw(canvas)
|
||||
if(state.touches.find { it.id >= 0 } == null)
|
||||
if(pointerTouches.values.find { !it.lifted } == null)
|
||||
return
|
||||
val drawable = if(state.buttons and ControllerState.BUTTON_TOUCHPAD != 0U) drawablePressed else drawableIdle
|
||||
drawable?.setBounds(paddingLeft, paddingTop, width - paddingRight, height - paddingBottom)
|
||||
drawable?.draw(canvas)
|
||||
}
|
||||
|
@ -59,23 +105,40 @@ class TouchpadView @JvmOverloads constructor(
|
|||
{
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
state.startTouch(touchX(event, event.actionIndex), touchY(event, event.actionIndex))?.let {
|
||||
pointerTouchIds[event.getPointerId(event.actionIndex)] = it
|
||||
val touch = Touch(it, event.getX(event.actionIndex), event.getY(event.actionIndex))
|
||||
pointerTouches[event.getPointerId(event.actionIndex)] = touch
|
||||
if(!buttonHeld)
|
||||
postDelayed(touch.startButtonHoldRunnable, BUTTON_HOLD_DELAY_MS)
|
||||
triggerStateChanged()
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
|
||||
pointerTouchIds.remove(event.getPointerId(event.actionIndex))?.let {
|
||||
state.stopTouch(it)
|
||||
pointerTouches.remove(event.getPointerId(event.actionIndex))?.let {
|
||||
removeCallbacks(it.startButtonHoldRunnable)
|
||||
when
|
||||
{
|
||||
buttonHeld ->
|
||||
{
|
||||
buttonHeld = false
|
||||
state.buttons = state.buttons and ControllerState.BUTTON_TOUCHPAD.inv()
|
||||
state.stopTouch(it.stateId)
|
||||
}
|
||||
it.moveInsignificant -> triggerShortButtonPress(it)
|
||||
else -> state.stopTouch(it.stateId)
|
||||
}
|
||||
triggerStateChanged()
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val changed = pointerTouchIds.entries.fold(false) { acc, it ->
|
||||
val changed = pointerTouches.entries.fold(false) { acc, it ->
|
||||
val index = event.findPointerIndex(it.key)
|
||||
if(index < 0)
|
||||
acc
|
||||
else
|
||||
acc || state.setTouchPos(it.value, touchX(event, index), touchY(event, index))
|
||||
{
|
||||
it.value.onMove(event.getX(event.actionIndex), event.getY(event.actionIndex))
|
||||
acc || state.setTouchPos(it.value.stateId, touchX(event, index), touchY(event, index))
|
||||
}
|
||||
}
|
||||
if(changed)
|
||||
triggerStateChanged()
|
||||
|
@ -84,6 +147,14 @@ class TouchpadView @JvmOverloads constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun triggerShortButtonPress(touch: Touch)
|
||||
{
|
||||
shortPressingTouches = shortPressingTouches + listOf(touch)
|
||||
removeCallbacks(shortButtonPressLiftRunnable)
|
||||
state.buttons = state.buttons or ControllerState.BUTTON_TOUCHPAD
|
||||
postDelayed(shortButtonPressLiftRunnable, SHORT_BUTTON_PRESS_DURATION_MS)
|
||||
}
|
||||
|
||||
private fun triggerStateChanged()
|
||||
{
|
||||
invalidate()
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="67.73333"
|
||||
android:viewportHeight="67.73334">
|
||||
<path
|
||||
android:pathData="M16.9333,4.2333C7.5523,4.2333 0,10.8416 0,19.05l0,29.6333c0,8.2084 7.5523,14.8167 16.9333,14.8167l33.8667,0c9.3811,0 16.9333,-6.6082 16.9333,-14.8167L67.7333,19.05C67.7333,10.8416 60.1811,4.2333 50.8,4.2333ZM11.8701,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042A3.7042,3.7042 0,0 1,22.9826 19.05,3.7042 3.7042,0 0,1 26.6867,15.3458ZM41.5034,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="22.6959"
|
||||
android:fillColor="@color/control_primary"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="67.73333"
|
||||
android:viewportHeight="67.73334">
|
||||
<path
|
||||
android:pathData="M16.9333,4.2333C7.5523,4.2333 0,10.8416 0,19.05l0,29.6333c0,8.2084 7.5523,14.8167 16.9333,14.8167l33.8667,0c9.3811,0 16.9333,-6.6082 16.9333,-14.8167L67.7333,19.05C67.7333,10.8416 60.1811,4.2333 50.8,4.2333ZM11.8701,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042A3.7042,3.7042 0,0 1,22.9826 19.05,3.7042 3.7042,0 0,1 26.6867,15.3458ZM41.5034,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,15.3458a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,30.1625a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,135 0,1 -3.7042,3.7042 3.7042,3.7042 135,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM11.8701,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM26.6867,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM41.5034,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042zM56.3201,44.9792a3.7042,3.7042 0,0 1,3.7042 3.7042,3.7042 3.7042,0 0,1 -3.7042,3.7042 3.7042,3.7042 0,0 1,-3.7042 -3.7042,3.7042 3.7042,0 0,1 3.7042,-3.7042z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="22.6959"
|
||||
android:fillColor="@color/control_pressed"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="1920dp"
|
||||
android:height="942dp"
|
||||
android:viewportWidth="508"
|
||||
android:viewportHeight="249.2375">
|
||||
<path
|
||||
android:pathData="M32,0L476,0A32,32 0,0 1,508 32L508,217.238A32,32 0,0 1,476 249.238L32,249.238A32,32 0,0 1,-0 217.238L-0,32A32,32 0,0 1,32 0z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.59637"
|
||||
android:fillColor="@color/control_pressed"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -51,8 +51,10 @@
|
|||
android:id="@+id/touchpadView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:drawable="@drawable/control_touchpad"
|
||||
app:layout_constraintTop_toBottomOf="@id/touchpadButtonView"
|
||||
app:drawableIdle="@drawable/control_touchpad"
|
||||
app:drawablePressed="@drawable/control_touchpad_pressed"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_max="300dp"
|
||||
app:layout_constraintDimensionRatio="1920:942"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
@ -169,17 +171,6 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
|
||||
<com.metallic.chiaki.touchcontrols.ButtonView
|
||||
android:id="@+id/touchpadButtonView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:padding="8dp"
|
||||
app:drawableIdle="@drawable/control_button_touchpad"
|
||||
app:drawablePressed="@drawable/control_button_touchpad_pressed"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<com.metallic.chiaki.touchcontrols.ButtonView
|
||||
android:id="@+id/l2ButtonView"
|
||||
android:layout_width="80dp"
|
||||
|
|
|
@ -13,23 +13,12 @@
|
|||
tools:layout_editor_absoluteX="0dp"
|
||||
tools:layout_editor_absoluteY="90dp" />
|
||||
|
||||
<com.metallic.chiaki.touchcontrols.ButtonView
|
||||
android:id="@+id/touchpadButtonView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="8dp"
|
||||
app:drawableIdle="@drawable/control_button_touchpad"
|
||||
app:drawablePressed="@drawable/control_button_touchpad_pressed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.metallic.chiaki.touchcontrols.TouchpadView
|
||||
android:id="@+id/touchpadView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:drawable="@drawable/control_touchpad"
|
||||
app:drawableIdle="@drawable/control_touchpad"
|
||||
app:drawablePressed="@drawable/control_touchpad_pressed"
|
||||
app:layout_constraintDimensionRatio="1920:942"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="drawableIdle" format="reference" />
|
||||
<attr name="drawablePressed" format="reference" />
|
||||
|
||||
<declare-styleable name="ButtonView">
|
||||
<attr name="drawableIdle" format="reference" />
|
||||
<attr name="drawablePressed" format="integer" />
|
||||
<attr name="drawableIdle" />
|
||||
<attr name="drawablePressed" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AnalogStickView">
|
||||
|
@ -13,6 +16,7 @@
|
|||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TouchpadView">
|
||||
<attr name="drawable" format="reference" />
|
||||
<attr name="drawableIdle" />
|
||||
<attr name="drawablePressed" />
|
||||
</declare-styleable>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue