Propagate Touchpad State through TouchControlsFragment on Android

This commit is contained in:
Florian Märkl 2021-01-15 15:32:37 +01:00
commit fa44a3269c
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
4 changed files with 56 additions and 20 deletions

View file

@ -24,6 +24,8 @@ import com.metallic.chiaki.lib.ConnectVideoProfile
import com.metallic.chiaki.session.* import com.metallic.chiaki.session.*
import com.metallic.chiaki.touchcontrols.TouchControlsFragment import com.metallic.chiaki.touchcontrols.TouchControlsFragment
import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment import com.metallic.chiaki.touchcontrols.TouchpadOnlyFragment
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import kotlin.math.min import kotlin.math.min
private sealed class DialogContents private sealed class DialogContents
@ -113,18 +115,25 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
} }
} }
private val controlsDisposable = CompositeDisposable()
override fun onAttachFragment(fragment: Fragment) override fun onAttachFragment(fragment: Fragment)
{ {
super.onAttachFragment(fragment) super.onAttachFragment(fragment)
if(fragment is TouchControlsFragment) when(fragment)
{ {
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it } is TouchControlsFragment ->
fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled {
} fragment.controllerState
if(fragment is TouchpadOnlyFragment) .subscribe { viewModel.input.touchControllerState = it }
{ .addTo(controlsDisposable)
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it } fragment.onScreenControlsEnabled = viewModel.onScreenControlsEnabled
fragment.touchpadOnlyEnabled = viewModel.touchpadOnlyEnabled }
is TouchpadOnlyFragment ->
{
fragment.controllerStateCallback = { viewModel.input.touchControllerState = it }
fragment.touchpadOnlyEnabled = viewModel.touchpadOnlyEnabled
}
} }
} }
@ -141,6 +150,12 @@ class StreamActivity : AppCompatActivity(), View.OnSystemUiVisibilityChangeListe
viewModel.session.pause() viewModel.session.pause()
} }
override fun onDestroy()
{
super.onDestroy()
controlsDisposable.dispose()
}
private fun reconnect() private fun reconnect()
{ {
viewModel.session.shutdown() viewModel.session.shutdown()

View file

@ -11,19 +11,31 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.metallic.chiaki.databinding.FragmentControlsBinding import com.metallic.chiaki.databinding.FragmentControlsBinding
import com.metallic.chiaki.lib.ControllerState import com.metallic.chiaki.lib.ControllerState
import io.reactivex.Observable
import io.reactivex.Observable.combineLatest
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
class TouchControlsFragment : Fragment() class TouchControlsFragment : Fragment()
{ {
private var controllerState = ControllerState() private var ownControllerState = ControllerState()
private set(value) private set(value)
{ {
val diff = field != value val diff = field != value
field = value field = value
if(diff) if(diff)
controllerStateCallback?.let { it(value) } ownControllerStateSubject.onNext(ownControllerState)
} }
var controllerStateCallback: ((ControllerState) -> Unit)? = null private val ownControllerStateSubject: Subject<ControllerState>
= BehaviorSubject.create<ControllerState>().also { it.onNext(ownControllerState) }
// to delay attaching to the touchpadView until it's available
private val controllerStateProxy: Subject<Observable<ControllerState>>
= BehaviorSubject.create<Observable<ControllerState>>().also { it.onNext(ownControllerStateSubject) }
val controllerState: Observable<ControllerState> get() =
controllerStateProxy.flatMap { it }
var onScreenControlsEnabled: LiveData<Boolean>? = null var onScreenControlsEnabled: LiveData<Boolean>? = null
private var _binding: FragmentControlsBinding? = null private var _binding: FragmentControlsBinding? = null
@ -32,6 +44,9 @@ class TouchControlsFragment : Fragment()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
FragmentControlsBinding.inflate(inflater, container, false).let { FragmentControlsBinding.inflate(inflater, container, false).let {
_binding = it _binding = it
controllerStateProxy.onNext(
combineLatest(ownControllerStateSubject, binding.touchpadView.controllerState) { a, b -> a or b }
)
it.root it.root
} }
@ -52,19 +67,19 @@ class TouchControlsFragment : Fragment()
binding.psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS) binding.psButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_PS)
binding.touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD) binding.touchpadButtonView.buttonPressedCallback = buttonStateChanged(ControllerState.BUTTON_TOUCHPAD)
binding.l2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { l2State = if(it) 255U else 0U } } binding.l2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { l2State = if(it) 255U else 0U } }
binding.r2ButtonView.buttonPressedCallback = { controllerState = controllerState.copy().apply { r2State = if(it) 255U else 0U } } binding.r2ButtonView.buttonPressedCallback = { ownControllerState = ownControllerState.copy().apply { r2State = if(it) 255U else 0U } }
val quantizeStick = { f: Float -> val quantizeStick = { f: Float ->
(Short.MAX_VALUE * f).toInt().toShort() (Short.MAX_VALUE * f).toInt().toShort()
} }
binding.leftAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply { binding.leftAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
leftX = quantizeStick(it.x) leftX = quantizeStick(it.x)
leftY = quantizeStick(it.y) leftY = quantizeStick(it.y)
}} }}
binding.rightAnalogStickView.stateChangedCallback = { controllerState = controllerState.copy().apply { binding.rightAnalogStickView.stateChangedCallback = { ownControllerState = ownControllerState.copy().apply {
rightX = quantizeStick(it.x) rightX = quantizeStick(it.x)
rightY = quantizeStick(it.y) rightY = quantizeStick(it.y)
}} }}
@ -76,7 +91,7 @@ class TouchControlsFragment : Fragment()
private fun dpadStateChanged(direction: DPadView.Direction?) private fun dpadStateChanged(direction: DPadView.Direction?)
{ {
controllerState = controllerState.copy().apply { ownControllerState = ownControllerState.copy().apply {
buttons = ((buttons buttons = ((buttons
and ControllerState.BUTTON_DPAD_LEFT.inv() and ControllerState.BUTTON_DPAD_LEFT.inv()
and ControllerState.BUTTON_DPAD_RIGHT.inv() and ControllerState.BUTTON_DPAD_RIGHT.inv()
@ -98,7 +113,7 @@ class TouchControlsFragment : Fragment()
} }
private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean -> private fun buttonStateChanged(buttonMask: UInt) = { pressed: Boolean ->
controllerState = controllerState.copy().apply { ownControllerState = ownControllerState.copy().apply {
buttons = buttons =
if(pressed) if(pressed)
buttons or buttonMask buttons or buttonMask

View file

@ -10,15 +10,20 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import com.metallic.chiaki.R import com.metallic.chiaki.R
import com.metallic.chiaki.lib.ControllerState import com.metallic.chiaki.lib.ControllerState
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.Subject
class TouchpadView @JvmOverloads constructor( class TouchpadView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) ) : View(context, attrs, defStyleAttr)
{ {
val state: ControllerState = ControllerState() private val state: ControllerState = ControllerState()
private val pointerTouchIds = mutableMapOf<Int, UByte>() private val pointerTouchIds = mutableMapOf<Int, UByte>()
var stateChangeCallback: ((ControllerState) -> Unit)? = null private val stateSubject: Subject<ControllerState>
= BehaviorSubject.create<ControllerState>().also { it.onNext(state) }
val controllerState: Observable<ControllerState> get() = stateSubject
private val drawable: Drawable? private val drawable: Drawable?
@ -81,6 +86,6 @@ class TouchpadView @JvmOverloads constructor(
private fun triggerStateChanged() private fun triggerStateChanged()
{ {
stateChangeCallback?.let { it(state) } stateSubject.onNext(state)
} }
} }

View file

@ -48,6 +48,7 @@
/> />
<com.metallic.chiaki.touchcontrols.TouchpadView <com.metallic.chiaki.touchcontrols.TouchpadView
android:id="@+id/touchpadView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:drawable="@drawable/control_touchpad" app:drawable="@drawable/control_touchpad"