From f8155ce06e3561bb405c6869b3e5848f0346b29c Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Thu, 18 Dec 2025 22:18:28 -0500 Subject: [PATCH] added some on screen buttons --- .../app/src/main/assets/Config/Supermodel.ini | 2 +- .../app/src/main/cpp/android_input_system.cpp | 6 +- .../app/src/main/cpp/android_input_system.h | 2 +- .../com/izzy2lost/super3/AssetInstaller.kt | 23 +- .../com/izzy2lost/super3/GameInputsIndex.kt | 120 ++ .../com/izzy2lost/super3/Super3Activity.kt | 108 + .../app/src/main/res/color/overlay_ripple.xml | 6 + .../app/src/main/res/drawable/brakepedal.xml | 1268 ++++++++++++ android/app/src/main/res/drawable/coin.xml | 1771 +++++++++++++++++ .../app/src/main/res/drawable/gaspedal.xml | 1193 +++++++++++ .../res/drawable/overlay_ripple_circle.xml | 10 + .../res/drawable/overlay_ripple_rounded.xml | 11 + .../src/main/res/layout/overlay_controls.xml | 107 + 13 files changed, 4621 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/java/com/izzy2lost/super3/GameInputsIndex.kt create mode 100644 android/app/src/main/res/color/overlay_ripple.xml create mode 100644 android/app/src/main/res/drawable/brakepedal.xml create mode 100644 android/app/src/main/res/drawable/coin.xml create mode 100644 android/app/src/main/res/drawable/gaspedal.xml create mode 100644 android/app/src/main/res/drawable/overlay_ripple_circle.xml create mode 100644 android/app/src/main/res/drawable/overlay_ripple_rounded.xml create mode 100644 android/app/src/main/res/layout/overlay_controls.xml diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index f9e8dbe..c3abc88 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -54,7 +54,7 @@ InputSteeringLeft = KEY_LEFT InputSteeringRight = KEY_RIGHT InputSteering = JOY1_XAXIS InputAccelerator = KEY_W,JOY1_RZAXIS_POS -InputBrake = KEY_S,JOY1_ZAXIS_POS +InputBrake = KEY_X,JOY1_ZAXIS_POS ; Manual transmission (LB/RB + 4-speed on face buttons) InputGearShiftUp = KEY_Y,JOY1_BUTTON6 diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index 694977d..814c10f 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -65,7 +65,7 @@ void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) m_touchSteerRight = {steerRight, (steerRight != joyRight) ? joyRight : SDL_SCANCODE_UNKNOWN}; m_touchThrottle.a = keySc(get("InputAccelerator", "KEY_W"), SDL_SCANCODE_W); - m_touchBrake.a = keySc(get("InputBrake", "KEY_S"), SDL_SCANCODE_S); + m_touchBrake.a = keySc(get("InputBrake", "KEY_X"), SDL_SCANCODE_X); } bool AndroidInputSystem::InitializeSystem() @@ -217,13 +217,13 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) // Tap zones (momentary): // - Bottom-left: Coin (KEY_5) - // - Bottom-right: Start (KEY_1) + // - Bottom-middle: Start (KEY_1) // - Top-left: Service (KEY_F1) // - Top-right: Test (KEY_F2) if (down) { if (x < 0.25f && y > 0.75f) { PulseKeys(m_touchCoin, 120); return; } - if (x > 0.75f && y > 0.75f) { PulseKeys(m_touchStart, 120); return; } + if (x > 0.40f && x < 0.60f && y > 0.75f) { PulseKeys(m_touchStart, 120); return; } if (x < 0.25f && y < 0.25f) { PulseKeys(m_touchService, 120); return; } if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; } } diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index d959455..77f528b 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -108,7 +108,7 @@ private: DualScancode m_touchSteerRight{SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN}; DualScancode m_touchThrottle{SDL_SCANCODE_W, SDL_SCANCODE_UNKNOWN}; - DualScancode m_touchBrake{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchBrake{SDL_SCANCODE_X, SDL_SCANCODE_UNKNOWN}; bool m_gunTouchEnabled = false; SDL_FingerID m_gunFinger = 0; diff --git a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt index 61ed7ca..b9f1a04 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt @@ -13,6 +13,8 @@ object AssetInstaller { for (dir in topLevel) { copyAssetTree(context, dir, File(internalUserRoot, dir)) } + + migrateSupermodelIni(File(File(internalUserRoot, "Config"), "Supermodel.ini")) } private fun copyAssetTree(context: Context, assetPath: String, dest: File) { @@ -37,5 +39,24 @@ object AssetInstaller { copyAssetTree(context, "$assetPath/$child", File(dest, child)) } } -} + private fun migrateSupermodelIni(ini: File) { + if (!ini.exists()) return + + val lines = runCatching { ini.readLines() }.getOrNull() ?: return + var changed = false + val updated = + lines.map { line -> + val trimmed = line.trim() + if (trimmed == "InputBrake = KEY_S,JOY1_ZAXIS_POS") { + changed = true + "InputBrake = KEY_X,JOY1_ZAXIS_POS" + } else { + line + } + } + + if (!changed) return + runCatching { ini.writeText(updated.joinToString(System.lineSeparator())) } + } +} diff --git a/android/app/src/main/java/com/izzy2lost/super3/GameInputsIndex.kt b/android/app/src/main/java/com/izzy2lost/super3/GameInputsIndex.kt new file mode 100644 index 0000000..1eb2578 --- /dev/null +++ b/android/app/src/main/java/com/izzy2lost/super3/GameInputsIndex.kt @@ -0,0 +1,120 @@ +package com.izzy2lost.super3 + +import android.util.Xml +import org.xmlpull.v1.XmlPullParser +import java.io.File +import java.io.InputStream + +private data class GameInputs( + val name: String, + val parent: String?, + val inputTypes: Set, +) + +object GameInputsIndex { + @Volatile + private var cachedFromPath: String? = null + + @Volatile + private var cached: Map? = null + + fun hasAnyInputType(gamesXmlPath: String, gameName: String, types: Set): Boolean { + val index = load(gamesXmlPath) + val visited = HashSet(8) + var cur: String? = gameName + while (cur != null && visited.add(cur)) { + val def = index[cur] + if (def != null) { + if (def.inputTypes.any { it in types }) return true + cur = def.parent + } else { + break + } + } + return false + } + + @Synchronized + private fun load(gamesXmlPath: String): Map { + val existingPath = cachedFromPath + val existing = cached + if (existing != null && existingPath == gamesXmlPath) return existing + + val file = File(gamesXmlPath) + val parsed = + if (file.exists()) { + file.inputStream().use { parse(it) } + } else { + emptyMap() + } + cachedFromPath = gamesXmlPath + cached = parsed + return parsed + } + + private fun parse(input: InputStream): Map { + val parser = Xml.newPullParser() + parser.setInput(input, null) + + val out = HashMap(256) + var event = parser.eventType + + var currentGameName: String? = null + var currentParent: String? = null + var inInputs = false + var types: MutableSet? = null + + fun finishGame() { + val name = currentGameName ?: return + out[name] = GameInputs(name = name, parent = currentParent, inputTypes = types?.toSet().orEmpty()) + } + + while (event != XmlPullParser.END_DOCUMENT) { + when (event) { + XmlPullParser.START_TAG -> { + when (parser.name) { + "game" -> { + currentGameName = parser.getAttributeValue(null, "name") + currentParent = parser.getAttributeValue(null, "parent") + inInputs = false + types = null + } + + "inputs" -> { + inInputs = true + if (types == null) types = LinkedHashSet() + } + + "input" -> { + if (inInputs) { + val t = parser.getAttributeValue(null, "type")?.trim().orEmpty() + if (t.isNotEmpty()) { + if (types == null) types = LinkedHashSet() + types?.add(t) + } + } + } + } + } + + XmlPullParser.END_TAG -> { + when (parser.name) { + "inputs" -> inInputs = false + "game" -> { + finishGame() + currentGameName = null + currentParent = null + inInputs = false + types = null + } + } + } + } + + event = parser.next() + } + + return out + } +} + diff --git a/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt b/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt index 4175519..e5d0be2 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt @@ -1,6 +1,13 @@ package com.izzy2lost.super3 import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.RelativeLayout import java.io.File import kotlin.concurrent.thread import org.libsdl.app.SDLActivity @@ -10,6 +17,8 @@ import org.libsdl.app.SDLActivity * library specified by SDL_MAIN_LIBRARY (set to "super3" in the manifest). */ class Super3Activity : SDLActivity() { + private var overlayView: View? = null + override fun getLibraries(): Array = arrayOf( "SDL2", "super3", @@ -29,6 +38,105 @@ class Super3Activity : SDLActivity() { return args.toTypedArray() } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val root = SDLActivity.getContentView() as? RelativeLayout ?: return + if (overlayView != null) return + + val overlay = LayoutInflater.from(this).inflate(R.layout.overlay_controls, root, false) + overlayView = overlay + root.addView( + overlay, + RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ), + ) + + val game = intent.getStringExtra("gameName").orEmpty() + val gamesXml = intent.getStringExtra("gamesXmlPath").orEmpty() + val isRacing = + game.isNotBlank() && + gamesXml.isNotBlank() && + GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("vehicle", "harley")) + + overlay.findViewById(R.id.overlay_pedals)?.visibility = + if (isRacing) View.VISIBLE else View.GONE + + fun nativeTouch(action: Int, fingerId: Int, x: Float, y: Float, p: Float = 1.0f) { + SDLActivity.onNativeTouch(0, fingerId, action, x, y, p) + } + + fun bindMomentary(viewId: Int, fingerId: Int, x: Float, y: Float) { + val v = overlay.findViewById(viewId) ?: return + v.setOnTouchListener { _, ev -> + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + v.alpha = 0.75f + nativeTouch(MotionEvent.ACTION_DOWN, fingerId, x, y) + true + } + MotionEvent.ACTION_UP -> { + v.alpha = 1.0f + nativeTouch(MotionEvent.ACTION_UP, fingerId, x, y) + true + } + MotionEvent.ACTION_CANCEL -> { + v.alpha = 1.0f + nativeTouch(MotionEvent.ACTION_UP, fingerId, x, y) + true + } + else -> true + } + } + } + + fun bindHeld(viewId: Int, fingerId: Int, x: Float, y: Float) { + val v = overlay.findViewById(viewId) ?: return + v.setOnTouchListener { _, ev -> + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + v.alpha = 0.75f + nativeTouch(MotionEvent.ACTION_DOWN, fingerId, x, y) + true + } + MotionEvent.ACTION_UP -> { + v.alpha = 1.0f + nativeTouch(MotionEvent.ACTION_UP, fingerId, x, y) + true + } + MotionEvent.ACTION_CANCEL -> { + v.alpha = 1.0f + nativeTouch(MotionEvent.ACTION_UP, fingerId, x, y) + true + } + else -> true + } + } + } + + // Use synthetic touch IDs that won't collide with real pointer IDs. + bindMomentary(R.id.overlay_coin, fingerId = 1101, x = 0.10f, y = 0.90f) + bindMomentary(R.id.overlay_start, fingerId = 1102, x = 0.50f, y = 0.90f) + bindMomentary(R.id.overlay_service, fingerId = 1105, x = 0.10f, y = 0.10f) + bindMomentary(R.id.overlay_test, fingerId = 1106, x = 0.90f, y = 0.10f) + + if (isRacing) { + // Match the native pedal zone (right-middle), independent of UI placement. + bindHeld(R.id.overlay_gas, fingerId = 1103, x = 0.85f, y = 0.35f) + bindHeld(R.id.overlay_brake, fingerId = 1104, x = 0.85f, y = 0.80f) + } + } + + override fun onDestroy() { + overlayView?.let { v -> + (v.parent as? ViewGroup)?.removeView(v) + } + overlayView = null + super.onDestroy() + } + override fun onResume() { super.onResume() applyImmersiveMode() diff --git a/android/app/src/main/res/color/overlay_ripple.xml b/android/app/src/main/res/color/overlay_ripple.xml new file mode 100644 index 0000000..7d5eb1e --- /dev/null +++ b/android/app/src/main/res/color/overlay_ripple.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/brakepedal.xml b/android/app/src/main/res/drawable/brakepedal.xml new file mode 100644 index 0000000..3cd5e23 --- /dev/null +++ b/android/app/src/main/res/drawable/brakepedal.xml @@ -0,0 +1,1268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/coin.xml b/android/app/src/main/res/drawable/coin.xml new file mode 100644 index 0000000..59b6a36 --- /dev/null +++ b/android/app/src/main/res/drawable/coin.xml @@ -0,0 +1,1771 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/gaspedal.xml b/android/app/src/main/res/drawable/gaspedal.xml new file mode 100644 index 0000000..b9aa3ce --- /dev/null +++ b/android/app/src/main/res/drawable/gaspedal.xml @@ -0,0 +1,1193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/overlay_ripple_circle.xml b/android/app/src/main/res/drawable/overlay_ripple_circle.xml new file mode 100644 index 0000000..52029f5 --- /dev/null +++ b/android/app/src/main/res/drawable/overlay_ripple_circle.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable/overlay_ripple_rounded.xml b/android/app/src/main/res/drawable/overlay_ripple_rounded.xml new file mode 100644 index 0000000..bce4700 --- /dev/null +++ b/android/app/src/main/res/drawable/overlay_ripple_rounded.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/layout/overlay_controls.xml b/android/app/src/main/res/layout/overlay_controls.xml new file mode 100644 index 0000000..fe887c1 --- /dev/null +++ b/android/app/src/main/res/layout/overlay_controls.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + +