Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Touch inputs in retrograde-tv-app #41

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildSrc/src/main/java/deps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ object deps {
}
}
object support {
const val supportCompat = "com.android.support:support-compat:${versions.support}"
const val appCompatV7 = "com.android.support:appcompat-v7:${versions.support}"
const val leanbackV17 = "com.android.support:leanback-v17:${versions.support}"
const val paletteV7 = "com.android.support:palette-v7:${versions.support}"
const val prefLeanbackV17 = "com.android.support:preference-leanback-v17:${versions.support}"
const val recyclerViewV7 = "com.android.support:recyclerview-v7:${versions.support}"
const val constraintLayout = "com.android.support.constraint:constraint-layout:1.1.3"
}
const val bugsnagAndroid = "com.bugsnag:bugsnag-android:4.9.2"
const val bugsnagAndroidNdk = "com.bugsnag:bugsnag-android-ndk:4.9.2"
Expand Down
3 changes: 3 additions & 0 deletions retrograde-app-tv/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ dependencies {
implementation(project(":retrograde-storage-webdav"))
implementation(project(":retrograde-storage-archiveorg"))

// TODO... This dependency will be gone when the separate mobile application is created.
implementation(project(":retrograde-touchinput"))

implementation(deps.libs.arch.paging)
implementation(deps.libs.arch.room.runtime)
implementation(deps.libs.arch.work.runtime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
Expand All @@ -48,6 +49,7 @@ import com.codebutler.retrograde.lib.util.subscribeBy
import com.gojuno.koptional.None
import com.gojuno.koptional.Some
import com.gojuno.koptional.toOptional
import com.swordfish.touchinput.pads.GamePadFactory
import com.uber.autodispose.android.lifecycle.scope
import com.uber.autodispose.kotlin.autoDisposable
import io.reactivex.android.schedulers.AndroidSchedulers
Expand All @@ -73,12 +75,15 @@ class GameActivity : RetrogradeActivity() {
private var game: Game? = null
private var retroDroid: RetroDroid? = null

private var displayTouchInput: Boolean = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)

val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val enableOpengl = prefs.getBoolean(getString(R.string.pref_key_flags_opengl), false)
displayTouchInput = prefs.getBoolean(getString(R.string.pref_key_flags_touchinput), false)

gameDisplay = if (enableOpengl) {
GlGameDisplay(this)
Expand Down Expand Up @@ -116,6 +121,34 @@ class GameActivity : RetrogradeActivity() {
}
}

private fun setupTouchInput(game: Game) {
val frameLayout = findViewById<FrameLayout>(R.id.game_layout)

val gameView = when (game.systemId) {
in listOf("snes", "gba") -> GamePadFactory.getGamePadView(this, GamePadFactory.Layout.SNES)
in listOf("nes", "gb", "gbc") -> GamePadFactory.getGamePadView(this, GamePadFactory.Layout.NES)
in listOf("md") -> GamePadFactory.getGamePadView(this, GamePadFactory.Layout.GENESIS)
else -> null
}

if (gameView != null) {
frameLayout.addView(gameView)

gameView.getEvents()
.doOnNext {
if (it.action == KeyEvent.ACTION_DOWN) {
performHapticFeedback(gameView)
}
}.autoDisposable(scope())
.subscribe { gameInput.onKeyEvent(KeyEvent(it.action, it.keycode)) }
}
}

private fun performHapticFeedback(view: View) {
val flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING or HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, flags)
}

override fun onDestroy() {
super.onDestroy()
// This activity runs in its own process which should not live beyond the activity lifecycle.
Expand Down Expand Up @@ -161,6 +194,10 @@ class GameActivity : RetrogradeActivity() {
val retroDroid = RetroDroid(gameDisplay, GameAudio(), gameInput, this, data.coreFile)
lifecycle.addObserver(retroDroid)

if (displayTouchInput) {
setupTouchInput(data.game)
}

retroDroid.gameUnloaded
.map { optionalSaveData ->
if (optionalSaveData is Some) {
Expand Down
1 change: 1 addition & 0 deletions retrograde-app-tv/src/main/res/values/keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<string name="pref_key_flags" translatable="false">flags</string>
<string name="pref_key_flags_logging" translatable="false">flags.debug_logging</string>
<string name="pref_key_flags_opengl" translatable="false">flags.opengl_rendering</string>
<string name="pref_key_flags_touchinput" translatable="false">flags.enable_touchinputs</string>
<string name="pref_key_sources" translatable="false">sources</string>
<string name="pref_key_licenses" translatable="false">licenses</string>
<string name="pref_key_version" translatable="false">version</string>
Expand Down
1 change: 1 addition & 0 deletions retrograde-app-tv/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<string name="onboarding_description">Retro gaming for Android TV</string>
<string name="onboarding_title">Welcome to Retrograde</string>
<string name="opengl_rendering">OpenGL Rendering</string>
<string name="display_touch_inputs">Display Touch Inputs</string>
<string name="play">Play</string>
<string name="recently_played">Recently Played</string>
<string name="remove_from_favorites">Remove from Favorites</string>
Expand Down
3 changes: 3 additions & 0 deletions retrograde-app-tv/src/main/res/xml/prefs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<SwitchPreference
android:key="@string/pref_key_flags_opengl"
android:title="@string/opengl_rendering"/>
<SwitchPreference
android:key="@string/pref_key_flags_touchinput"
android:title="@string/display_touch_inputs"/>
</PreferenceScreen>
<Preference
android:key="@string/pref_key_advanced_log"
Expand Down
22 changes: 22 additions & 0 deletions retrograde-touchinput/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
}

dependencies {
implementation(project(":retrograde-util"))
implementation(project(":retrograde-app-shared"))
implementation(project(":retrograde-metadata-ovgdb"))

implementation(deps.libs.rxJava2)
implementation(deps.libs.rxKotlin2)
implementation(deps.libs.rxKotlin2)
implementation(deps.libs.rxAndroid2)
implementation(deps.libs.rxRelay2)
implementation(deps.libs.support.constraintLayout)
implementation(deps.libs.support.appCompatV7)
implementation(deps.libs.support.supportCompat)

kapt(deps.libs.dagger.compiler)
}
1 change: 1 addition & 0 deletions retrograde-touchinput/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.swordfish.touchinput.controller" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.swordfish.touchinput.data

data class ButtonEvent(val action: Int, val index: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.swordfish.touchinput.data

import android.view.KeyEvent
import com.swordfish.touchinput.utils.observableOf
import com.swordfish.touchinput.views.DirectionPad
import io.reactivex.Observable
import io.reactivex.ObservableTransformer
import java.security.InvalidParameterException

internal object EventsTransformers {
fun actionButtonsMap(vararg keycodes: Int): ObservableTransformer<ButtonEvent, PadEvent> {
return ObservableTransformer { upstream ->
upstream.map { PadEvent(it.action, keycodes[it.index]) }
}
}

fun singleButtonMap(keycode: Int): ObservableTransformer<ButtonEvent, PadEvent> {
return ObservableTransformer { upstream ->
upstream.map { PadEvent(it.action, keycode) }
}
}

fun directionPadMap(): ObservableTransformer<ButtonEvent, PadEvent> {
return ObservableTransformer { upstream ->
upstream.flatMap { buttonEvent ->
mapDirectionToKey(buttonEvent.index).map { PadEvent(buttonEvent.action, it) }
}
}
}

private fun mapDirectionToKey(index: Int): Observable<Int> {
return when (index) {
DirectionPad.BUTTON_LEFT -> observableOf(KeyEvent.KEYCODE_DPAD_LEFT)
DirectionPad.BUTTON_RIGHT -> observableOf(KeyEvent.KEYCODE_DPAD_RIGHT)
DirectionPad.BUTTON_UP -> observableOf(KeyEvent.KEYCODE_DPAD_UP)
DirectionPad.BUTTON_DOWN -> observableOf(KeyEvent.KEYCODE_DPAD_DOWN)
DirectionPad.BUTTON_UP_LEFT -> observableOf(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP)
DirectionPad.BUTTON_UP_RIGHT -> observableOf(KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT)
DirectionPad.BUTTON_DOWN_LEFT -> observableOf(KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT)
DirectionPad.BUTTON_DOWN_RIGHT -> observableOf(KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT)
else -> throw InvalidParameterException("Invalid direction event with index: $index")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.swordfish.touchinput.data

data class PadEvent(val action: Int, val keycode: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.swordfish.touchinput.interfaces

import com.swordfish.touchinput.data.ButtonEvent
import io.reactivex.Observable

interface ButtonEventsSource {
fun getEvents(): Observable<ButtonEvent>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.swordfish.touchinput.pads

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import com.swordfish.touchinput.data.PadEvent
import io.reactivex.Observable

abstract class BaseGamePad @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

abstract fun getEvents(): Observable<PadEvent>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.swordfish.touchinput.pads

import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import com.swordfish.touchinput.controller.R
import com.swordfish.touchinput.data.EventsTransformers
import com.swordfish.touchinput.data.PadEvent
import com.swordfish.touchinput.views.ActionButtons
import com.swordfish.touchinput.views.DirectionPad
import com.swordfish.touchinput.views.LargeSingleButton
import com.swordfish.touchinput.views.SmallSingleButton
import io.reactivex.Observable

class GameBoyAdvancePad @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseGamePad(context, attrs, defStyleAttr) {

init {
inflate(context, R.layout.layout_gba, this)
}

override fun getEvents(): Observable<PadEvent> {
return Observable.merge(listOf(
getStartEvent(),
getSelectEvent(),
getDirectionEvents(),
getActionEvents(),
getR1Events(),
getL1Events()))
}

private fun getStartEvent(): Observable<PadEvent> {
return findViewById<SmallSingleButton>(R.id.gba_start)
.getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_START))
}

private fun getSelectEvent(): Observable<PadEvent> {
return findViewById<SmallSingleButton>(R.id.gba_select)
.getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_SELECT))
}

private fun getActionEvents(): Observable<PadEvent> {
return findViewById<ActionButtons>(R.id.gba_actions)
.getEvents()
.compose(EventsTransformers.actionButtonsMap(KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_A))
}

private fun getDirectionEvents(): Observable<PadEvent> {
return findViewById<DirectionPad>(R.id.gba_direction).getEvents()
.compose(EventsTransformers.directionPadMap())
}

private fun getL1Events(): Observable<PadEvent> {
return findViewById<LargeSingleButton>(R.id.gba_l1).getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_L1))
}

private fun getR1Events(): Observable<PadEvent> {
return findViewById<LargeSingleButton>(R.id.gba_r1).getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_R1))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.swordfish.touchinput.pads

import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import com.swordfish.touchinput.controller.R
import com.swordfish.touchinput.data.EventsTransformers
import com.swordfish.touchinput.data.PadEvent
import com.swordfish.touchinput.views.ActionButtons
import com.swordfish.touchinput.views.DirectionPad
import com.swordfish.touchinput.views.base.BaseSingleButton
import io.reactivex.Observable

class GameBoyPad @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseGamePad(context, attrs, defStyleAttr) {

init {
inflate(context, R.layout.layout_gb, this)
}

override fun getEvents(): Observable<PadEvent> {
return Observable.merge(
getStartEvent(),
getSelectEvent(),
getDirectionEvents(),
getActionEvents()
)
}

private fun getStartEvent(): Observable<PadEvent> {
return findViewById<BaseSingleButton>(R.id.gb_start)
.getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_START))
}

private fun getSelectEvent(): Observable<PadEvent> {
return findViewById<BaseSingleButton>(R.id.gb_select)
.getEvents()
.compose(EventsTransformers.singleButtonMap(KeyEvent.KEYCODE_BUTTON_SELECT))
}

private fun getActionEvents(): Observable<PadEvent> {
return findViewById<ActionButtons>(R.id.gb_actions)
.getEvents()
.compose(EventsTransformers.actionButtonsMap(KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_A))
}

private fun getDirectionEvents(): Observable<PadEvent> {
return findViewById<DirectionPad>(R.id.gb_direction).getEvents()
.compose(EventsTransformers.directionPadMap())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.swordfish.touchinput.pads

import android.content.Context

class GamePadFactory {
enum class Layout {
NES,
SNES,
GENESIS
}

companion object {
fun getGamePadView(context: Context, layout: Layout): BaseGamePad {
return when (layout) {
Layout.NES -> GameBoyPad(context)
Layout.SNES -> GameBoyAdvancePad(context)
Layout.GENESIS -> GenesisPad(context)
}
}
}
}
Loading