diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt
new file mode 100644
index 0000000..5767532
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/AnalogStickView.kt
@@ -0,0 +1,136 @@
+/*
+ * This file is part of Chiaki.
+ *
+ * Chiaki is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Chiaki is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Chiaki. If not, see .
+ */
+
+package com.metallic.chiaki.touchcontrols
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import com.metallic.chiaki.R
+import kotlin.math.abs
+
+class AnalogStickView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr)
+{
+ val radius: Float
+ private val handleRadius: Float
+ private val drawableBase: Drawable?
+ private val drawableHandle: Drawable?
+
+ var state = Vector(0f, 0f)
+ private set(value)
+ {
+ field = value
+ stateChangedCallback?.let { it(field) }
+ }
+
+ var stateChangedCallback: ((Vector) -> Unit)? = null
+
+ private val touchTracker = TouchTracker().also {
+ it.positionChangedCallback = this::updateState
+ }
+
+ private var center: Vector? = null
+
+ /**
+ * Same as state, but scaled to the circle
+ */
+ private var handlePosition: Vector = Vector(0f, 0f)
+
+ private val clipBoundsTmp = Rect()
+
+ init
+ {
+ context.theme.obtainStyledAttributes(attrs, R.styleable.AnalogStickView, 0, 0).apply {
+ radius = getDimension(R.styleable.AnalogStickView_radius, 0f)
+ handleRadius = getDimension(R.styleable.AnalogStickView_handleRadius, 0f)
+ drawableBase = getDrawable(R.styleable.AnalogStickView_drawableBase)
+ drawableHandle = getDrawable(R.styleable.AnalogStickView_drawableHandle)
+ recycle()
+ }
+ }
+
+ override fun onDraw(canvas: Canvas)
+ {
+ super.onDraw(canvas)
+
+ val center = center
+ if(center != null)
+ {
+ drawableBase?.setBounds((center.x - radius).toInt(), (center.y - radius).toInt(), (center.x + radius).toInt(), (center.y + radius).toInt())
+ drawableBase?.draw(canvas)
+
+ val handleX = center.x + handlePosition.x * radius
+ val handleY = center.y + handlePosition.y * radius
+ drawableHandle?.setBounds((handleX - handleRadius).toInt(), (handleY - handleRadius).toInt(), (handleX + handleRadius).toInt(),(handleY + handleRadius).toInt())
+ drawableHandle?.draw(canvas)
+ }
+ }
+
+ private fun updateState(position: Vector?)
+ {
+ if(radius <= 0f)
+ return
+
+ if(position == null)
+ {
+ center = null
+ state = Vector(0f, 0f)
+ handlePosition = Vector(0f, 0f)
+ invalidate()
+ return
+ }
+
+ val center: Vector = this.center ?: position
+ this.center = center
+
+ val dir = position - center
+ val length = dir.length
+ if(length > 0)
+ {
+ val strength = if(length > radius) 1.0f else length / radius
+ val dirNormalized = dir / length
+ handlePosition = dirNormalized * strength
+ val dirBoxNormalized =
+ if(abs(dirNormalized.x) > abs(dirNormalized.y))
+ dirNormalized / abs(dirNormalized.x)
+ else
+ dirNormalized / abs(dirNormalized.y)
+ state = dirBoxNormalized * strength
+ }
+ else
+ {
+ handlePosition = Vector(0f, 0f)
+ state = Vector(0f, 0f)
+ }
+
+ invalidate()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean
+ {
+ touchTracker.touchEvent(event)
+ return true
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
index a997b79..d02b5c7 100644
--- a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/DPadView.kt
@@ -20,13 +20,11 @@ package com.metallic.chiaki.touchcontrols
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
-import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import com.metallic.chiaki.R
import kotlin.math.abs
-import kotlin.math.sqrt
class DPadView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
@@ -48,7 +46,9 @@ class DPadView @JvmOverloads constructor(
private val dpadIdleDrawable = VectorDrawableCompat.create(resources, R.drawable.control_dpad_idle, null)
private val dpadLeftDrawable = VectorDrawableCompat.create(resources, R.drawable.control_dpad_left, null)
- private var pointerId: Int? = null
+ private val touchTracker = TouchTracker().also {
+ it.positionChangedCallback = this::updateState
+ }
override fun onDraw(canvas: Canvas)
{
@@ -75,10 +75,10 @@ class DPadView @JvmOverloads constructor(
drawable?.draw(canvas)
}
- private fun directionForPosition(x: Float, y: Float): Direction
+ private fun directionForPosition(position: Vector): Direction
{
- val dx = x - width * 0.5f
- val dy = y - height * 0.5f
+ val dx = position.x - width * 0.5f
+ val dy = position.y - height * 0.5f
return when
{
dx > abs(dy) -> Direction.RIGHT
@@ -88,20 +88,20 @@ class DPadView @JvmOverloads constructor(
}
}
- private fun updateState(x: Float?, y: Float?)
+ private fun updateState(position: Vector?)
{
val newState =
- if(x == null || y == null)
+ if(position == null)
null
else
{
- val xFrac = 2.0f * (x / width.toFloat() - 0.5f)
- val yFrac = 2.0f * (y / height.toFloat() - 0.5f)
+ val xFrac = 2.0f * (position.x / width.toFloat() - 0.5f)
+ val yFrac = 2.0f * (position.y / height.toFloat() - 0.5f)
val radiusSq = xFrac * xFrac + yFrac * yFrac
if(radiusSq < deadzoneRadius * deadzoneRadius && state != null)
state
else
- directionForPosition(x, y)
+ directionForPosition(position)
}
if(state != newState)
@@ -114,37 +114,7 @@ class DPadView @JvmOverloads constructor(
override fun onTouchEvent(event: MotionEvent): Boolean
{
- when(event.actionMasked)
- {
- MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN ->
- {
- if(pointerId == null)
- {
- pointerId = event.getPointerId(event.actionIndex)
- updateState(event.x, event.y)
- }
- }
-
- MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP ->
- {
- if(event.getPointerId(event.actionIndex) == pointerId)
- {
- pointerId = null
- updateState(null, null)
- }
- }
-
- MotionEvent.ACTION_MOVE ->
- {
- val pointerId = pointerId
- if(pointerId != null)
- {
- val pointerIndex = event.findPointerIndex(pointerId)
- if(pointerIndex >= 0)
- updateState(event.getX(pointerIndex), event.getY(pointerIndex))
- }
- }
- }
+ touchTracker.touchEvent(event)
return true
}
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 5704658..a4794d3 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
@@ -18,6 +18,7 @@
package com.metallic.chiaki.touchcontrols
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -61,30 +62,18 @@ class TouchControlsFragment : Fragment()
l2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { l2State = if(it) 255U else 0U } }
r2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { r2State = if(it) 255U else 0U } }
- leftDpadView.stateChangeCallback = { controllerState = controllerState.copy().apply {
- val pos: Pair = when(it)
- {
- DPadView.Direction.UP -> Pair(0, Short.MIN_VALUE)
- DPadView.Direction.DOWN -> Pair(0, Short.MAX_VALUE)
- DPadView.Direction.LEFT -> Pair(Short.MIN_VALUE, 0)
- DPadView.Direction.RIGHT -> Pair(Short.MAX_VALUE, 0)
- null -> Pair(0, 0)
- }
- leftX = pos.first
- leftY = pos.second
+ val quantizeStick = { f: Float ->
+ (Short.MAX_VALUE * f).toShort()
+ }
+
+ leftAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply {
+ leftX = quantizeStick(it.x)
+ leftY = quantizeStick(it.y)
}}
- rightDpadView.stateChangeCallback = { controllerState = controllerState.copy().apply {
- val pos: Pair = when(it)
- {
- DPadView.Direction.UP -> Pair(0, Short.MIN_VALUE)
- DPadView.Direction.DOWN -> Pair(0, Short.MAX_VALUE)
- DPadView.Direction.LEFT -> Pair(Short.MIN_VALUE, 0)
- DPadView.Direction.RIGHT -> Pair(Short.MAX_VALUE, 0)
- null -> Pair(0, 0)
- }
- rightX = pos.first
- rightY = pos.second
+ rightAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply {
+ rightX = quantizeStick(it.x)
+ rightY = quantizeStick(it.y)
}}
}
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt
new file mode 100644
index 0000000..0d82e06
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/TouchTracker.kt
@@ -0,0 +1,69 @@
+/*
+ * This file is part of Chiaki.
+ *
+ * Chiaki is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Chiaki is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Chiaki. If not, see .
+ */
+
+package com.metallic.chiaki.touchcontrols
+
+import android.view.MotionEvent
+
+class TouchTracker
+{
+ var currentPosition: Vector? = null
+ private set(value)
+ {
+ field = value
+ positionChangedCallback?.let { it(field) }
+ }
+
+ var positionChangedCallback: ((Vector?) -> Unit)? = null
+
+ private var pointerId: Int? = null
+
+ fun touchEvent(event: MotionEvent)
+ {
+ when(event.actionMasked)
+ {
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN ->
+ {
+ if(pointerId == null)
+ {
+ pointerId = event.getPointerId(event.actionIndex)
+ currentPosition = Vector(event.x, event.y)
+ }
+ }
+
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP ->
+ {
+ if(event.getPointerId(event.actionIndex) == pointerId)
+ {
+ pointerId = null
+ currentPosition = null
+ }
+ }
+
+ MotionEvent.ACTION_MOVE ->
+ {
+ val pointerId = pointerId
+ if(pointerId != null)
+ {
+ val pointerIndex = event.findPointerIndex(pointerId)
+ if(pointerIndex >= 0)
+ currentPosition = Vector(event.getX(pointerIndex), event.getY(pointerIndex))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt
new file mode 100644
index 0000000..96c236b
--- /dev/null
+++ b/android/app/src/main/java/com/metallic/chiaki/touchcontrols/Vector.kt
@@ -0,0 +1,31 @@
+/*
+ * This file is part of Chiaki.
+ *
+ * Chiaki is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Chiaki is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Chiaki. If not, see .
+ */
+
+package com.metallic.chiaki.touchcontrols
+
+import kotlin.math.sqrt
+
+data class Vector(val x: Float, val y: Float)
+{
+ operator fun plus(o: Vector) = Vector(x + o.x, y + o.y)
+ operator fun minus(o: Vector) = Vector(x - o.x, y - o.y)
+ operator fun times(s: Float) = Vector(x * s, y * s)
+ operator fun div(s: Float) = this * (1f / s)
+
+ val lengthSq get() = x*x + y*y
+ val length get() = sqrt(lengthSq)
+}
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/control_analog_stick_base.xml b/android/app/src/main/res/drawable/control_analog_stick_base.xml
new file mode 100644
index 0000000..bbe2070
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_analog_stick_base.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/control_analog_stick_handle.xml b/android/app/src/main/res/drawable/control_analog_stick_handle.xml
new file mode 100644
index 0000000..109df6a
--- /dev/null
+++ b/android/app/src/main/res/drawable/control_analog_stick_handle.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/android/app/src/main/res/layout/fragment_controls.xml b/android/app/src/main/res/layout/fragment_controls.xml
index ff89894..7cf6d9c 100644
--- a/android/app/src/main/res/layout/fragment_controls.xml
+++ b/android/app/src/main/res/layout/fragment_controls.xml
@@ -2,12 +2,48 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:clipChildren="false">
+
+
+
+
+
+
-
-
-
-
\ No newline at end of file
diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml
index 8ef2b4d..b0a1170 100644
--- a/android/app/src/main/res/values/attrs.xml
+++ b/android/app/src/main/res/values/attrs.xml
@@ -4,4 +4,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index 27bd845..3573e86 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -1,4 +1,6 @@
48dp
+ 64dp
+ 16dp
\ No newline at end of file
diff --git a/assets/controls/stick.svg b/assets/controls/stick.svg
new file mode 100644
index 0000000..96bbe17
--- /dev/null
+++ b/assets/controls/stick.svg
@@ -0,0 +1,64 @@
+
+
+
+