From bae081d5b319385ce6023bd4cc762f6c20f722e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 15 Jan 2021 18:00:05 +0100 Subject: [PATCH] Trigger Touchpad Button from TouchpadView on Android --- .../touchcontrols/TouchControlsFragment.kt | 1 - .../touchcontrols/TouchpadOnlyFragment.kt | 4 - .../chiaki/touchcontrols/TouchpadView.kt | 89 +++++++++++++++++-- .../res/drawable/control_button_touchpad.xml | 12 --- .../control_button_touchpad_pressed.xml | 12 --- .../res/drawable/control_touchpad_pressed.xml | 12 +++ .../src/main/res/layout/fragment_controls.xml | 17 +--- .../res/layout/fragment_touchpad_only.xml | 15 +--- android/app/src/main/res/values/attrs.xml | 10 ++- 9 files changed, 105 insertions(+), 67 deletions(-) delete mode 100644 android/app/src/main/res/drawable/control_button_touchpad.xml delete mode 100644 android/app/src/main/res/drawable/control_button_touchpad_pressed.xml create mode 100644 android/app/src/main/res/drawable/control_touchpad_pressed.xml diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt index b3ab922..b1ecfbe 100644 --- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt +++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchControlsFragment.kt @@ -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 } } diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt index 788b6d5..8910ef7 100644 --- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt +++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadOnlyFragment.kt @@ -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 }) diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt index b712c6e..9dba03d 100644 --- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt +++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchpadView.kt @@ -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() + + 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() private val stateSubject: Subject = BehaviorSubject.create().also { it.onNext(state) } val controllerState: Observable get() = stateSubject - private val drawable: Drawable? + private var shortPressingTouches = listOf() + 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() diff --git a/android/app/src/main/res/drawable/control_button_touchpad.xml b/android/app/src/main/res/drawable/control_button_touchpad.xml deleted file mode 100644 index 40c0465..0000000 --- a/android/app/src/main/res/drawable/control_button_touchpad.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml b/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml deleted file mode 100644 index 8255860..0000000 --- a/android/app/src/main/res/drawable/control_button_touchpad_pressed.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/control_touchpad_pressed.xml b/android/app/src/main/res/drawable/control_touchpad_pressed.xml new file mode 100644 index 0000000..b4f3f16 --- /dev/null +++ b/android/app/src/main/res/drawable/control_touchpad_pressed.xml @@ -0,0 +1,12 @@ + + + diff --git a/android/app/src/main/res/layout/fragment_controls.xml b/android/app/src/main/res/layout/fragment_controls.xml index ee2a4a8..244956a 100644 --- a/android/app/src/main/res/layout/fragment_controls.xml +++ b/android/app/src/main/res/layout/fragment_controls.xml @@ -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"/> - - - - + + + - - + + @@ -13,6 +16,7 @@ - + + \ No newline at end of file