From 12545bc74abafd1086b5f311d01d0ec0ae6a8ae8 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 11:04:02 -0400 Subject: [PATCH 01/10] Add a global Device Profile, with Astra 2 touch layouts and sticky button presses WinNative shipped one set of bundled touch layouts for every device. Their normalized coordinates are resolution-independent, but element size is derived from view width alone (snappingSize = width/100), so on a 16:10 tablet the same layout stretches vertically relative to its own buttons and the inboard controls drift out of thumb reach. Measured on a RedMagic Astra 2 (2400x1504 at 310.6 x 312.6 dpi = 195.0 x 123.0 mm, 9.08 in): with the grip pivot 5 mm outboard of the panel and 37 mm up from the bottom, the stock layouts put the D-pad at 67 mm and the right stick at 66 mm from the thumb pivot, and the inboard face button at 55 mm. Comfortable thumb travel on a tablet held in two hands is roughly 42 mm neutral, 53 mm comfortable, 64 mm stretch. Three of those controls were past the limit for their usage tier. Device Profile DeviceProfile classifies the host from the marketing-name properties (ro.vendor.product.ztename, ro.vendor.feature.nubia.exif.module) and a Build token haystack, so all regional Astra 2 SKUs match (PQ85P01 board family, NP06J model family) while other RedMagic and nubia devices do not. Anything unmatched is Default. PluviaApp seeds the choice once, after the child-process guard; the user can override it from the new Settings > Device page, and the override is never overwritten by later detection. The profile is a global app setting built as the single seam for per-device defaults. Input control layouts and the adaptive-joystick recommendation are wired up now; library icon sizing, session menu button sizing and per-game recommended settings are listed on the page as Coming Soon and only need their resolver pointed at DeviceProfileSettings. Bundled layouts Device variants live in a sibling asset directory (inputcontrols/profiles-astra2) rather than a subdirectory of profiles/. AssetManager.list returns directories too, and the sync loop opens every entry inside a single try block, so a nested variant directory would throw FileNotFoundException on the directory entry and abandon the rest of the loop - with the gate prefs already committed, that would silently skip the sync until the next app version. The listing now also filters to .icp, and the gate commits after the copy loop rather than before it. Switching device profile re-enters the sync through a third gate key. A working layout is replaced only when it still matches its pristine backup (FileUtils.contentEquals); an edited layout is left alone, while its backup is refreshed so Reset offers the layout bundled for the current device. Profile ids stay 1/2/3/6/7/8 in every variant because shortcuts persist the id and both the drawer and the shortcut dialog index by list position. All six Astra 2 layouts follow a DualSense split: D-pad above the left stick, ABXY above the right stick, triggers in the top outer corners for the index fingers, Start and Select centred out of both thumb arcs. L3 sits on the right half and R3 on the left, since the thumb owning a stick cannot also click it. Every gameplay control is inside its tier limit, no two elements overlap, and nothing sits closer than 4 mm to a neighbour or 6 mm to a screen edge. Press semantics A BUTTON no longer releases when the finger slides outside its bounds; it is held until the pointer actually lifts, which is what the release-on-exit branch in handleTouchMove used to prevent. Bundled layouts never persist the swipeable flag, so its true default made every shipped button a swipe target and the old behaviour dropped a held button on any drift. A pointer now captures a list of elements instead of one, so stacked buttons both fire from a single touch. The first accepting element is captured whatever its type, further BUTTONs at the same point are added, and an expanded radial menu suppresses co-capture because its hit test balloons well past its box. ACTION_DOWN and ACTION_UP run inside the batching window so one pointer pressing several bindings still produces a single gamepad state send. Sliding a held button onto another swipe target now adds it rather than swapping, and the D-pad and radial hand-off path is unchanged. The L3/R3 minimum-hold release carried no pointer identity, so a re-press within 300 ms was cancelled by the previous release's pending callback; it now checks a press generation. releaseActiveTouchElements also syncs the captured-pointer set, which it previously left stale for TouchpadView for one gesture. Adaptive joysticks The four sites that resolved the container default to a hard-coded "0" now read the profile recommendation, so Astra 2 containers that have never set the extra default to adaptive. Containers with an explicit value are untouched. Also back-fills library_games_store_none_owned, which was added to the base strings without translations, and adds the 34 new Device Profile strings to all 22 locales. --- app/src/main/app/PluviaApp.kt | 4 + app/src/main/app/config/DeviceProfile.kt | 92 ++++ .../main/app/config/DeviceProfileSettings.kt | 97 ++++ .../profiles-astra2/controls-1.icp | 265 ++++++++++ .../profiles-astra2/controls-2.icp | 215 ++++++++ .../profiles-astra2/controls-3.icp | 247 +++++++++ .../profiles-astra2/controls-6.icp | 281 ++++++++++ .../profiles-astra2/controls-7.icp | 247 +++++++++ .../profiles-astra2/controls-8.icp | 231 +++++++++ .../ContainerSettingsComposeDialog.kt | 6 +- .../settings/device/DeviceProfileScreen.kt | 478 ++++++++++++++++++ .../feature/settings/nav/SettingsNavGraph.kt | 3 + .../settings/nav/SettingsNavSidebar.kt | 8 + .../ShortcutSettingsComposeDialog.kt | 16 +- app/src/main/res/values-b+es+419/strings.xml | 37 ++ app/src/main/res/values-da/strings.xml | 37 ++ app/src/main/res/values-de/strings.xml | 37 ++ app/src/main/res/values-es/strings.xml | 37 ++ app/src/main/res/values-fi/strings.xml | 37 ++ app/src/main/res/values-fr/strings.xml | 37 ++ app/src/main/res/values-hi/strings.xml | 36 ++ app/src/main/res/values-it/strings.xml | 37 ++ app/src/main/res/values-ja/strings.xml | 37 ++ app/src/main/res/values-ko/strings.xml | 37 ++ app/src/main/res/values-no/strings.xml | 37 ++ app/src/main/res/values-pl/strings.xml | 37 ++ app/src/main/res/values-pt-rBR/strings.xml | 37 ++ app/src/main/res/values-pt/strings.xml | 37 ++ app/src/main/res/values-ro/strings.xml | 37 ++ app/src/main/res/values-ru/strings.xml | 37 ++ app/src/main/res/values-sv/strings.xml | 37 ++ app/src/main/res/values-th/strings.xml | 37 ++ app/src/main/res/values-tr/strings.xml | 37 ++ app/src/main/res/values-uk/strings.xml | 37 ++ app/src/main/res/values-zh-rCN/strings.xml | 37 ++ app/src/main/res/values-zh-rTW/strings.xml | 37 ++ app/src/main/res/values/refs.xml | 1 + app/src/main/res/values/strings.xml | 36 ++ .../display/XServerDisplayActivity.java | 6 +- .../input/controls/ControlElement.java | 7 +- .../input/controls/InputControlsManager.java | 91 +++- .../runtime/input/ui/InputControlsView.java | 197 +++++--- .../app/config/BundledControlLayoutTest.kt | 319 ++++++++++++ .../cmod/app/config/DeviceProfileTest.kt | 83 +++ 44 files changed, 3652 insertions(+), 91 deletions(-) create mode 100644 app/src/main/app/config/DeviceProfile.kt create mode 100644 app/src/main/app/config/DeviceProfileSettings.kt create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp create mode 100644 app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp create mode 100644 app/src/main/feature/settings/device/DeviceProfileScreen.kt create mode 100644 app/src/test/kotlin/com/winlator/cmod/app/config/BundledControlLayoutTest.kt create mode 100644 app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt diff --git a/app/src/main/app/PluviaApp.kt b/app/src/main/app/PluviaApp.kt index 2eed46339..96e01900d 100644 --- a/app/src/main/app/PluviaApp.kt +++ b/app/src/main/app/PluviaApp.kt @@ -3,6 +3,7 @@ import android.app.Activity import android.app.Application import android.os.Bundle import android.util.Log +import com.winlator.cmod.app.config.DeviceProfileSettings import com.winlator.cmod.app.db.PluviaDatabase import com.winlator.cmod.app.update.UpdateService import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager @@ -65,6 +66,9 @@ class PluviaApp : Application() { // Cached probe for devices whose native stack still needs system libjpeg preloaded. preloadSystemLibraries() + runCatching { DeviceProfileSettings.seedFromDetection(this) } + .onFailure { Log.w("PluviaApp", "device profile detection failed", it) } + registerRefreshRateLifecycleCallbacks() PrefManager.install(this) diff --git a/app/src/main/app/config/DeviceProfile.kt b/app/src/main/app/config/DeviceProfile.kt new file mode 100644 index 000000000..d37f65bcb --- /dev/null +++ b/app/src/main/app/config/DeviceProfile.kt @@ -0,0 +1,92 @@ +package com.winlator.cmod.app.config + +import android.os.Build +import android.util.Log +import com.winlator.cmod.R +import java.util.Locale + +enum class DeviceProfile( + val prefValue: String, + val assetToken: String, + val titleRes: Int, + val summaryRes: Int, +) { + DEFAULT("default", "", R.string.device_profile_default, R.string.device_profile_default_summary), + ASTRA_2("astra2", "astra2", R.string.device_profile_astra_2, R.string.device_profile_astra_2_summary), + ; + + companion object { + private const val TAG = "DeviceProfile" + + private val MARKETING_NAME_KEYS = + listOf( + "ro.vendor.product.ztename", + "ro.vendor.feature.nubia.exif.module", + "ro.product.marketname", + "ro.vendor.product.marketname", + ) + + private val ASTRA_2_NAME_TOKENS = listOf("astra 2", "astra2") + + private val ASTRA_2_BUILD_TOKENS = listOf("pq85p01", "np06j", "np06p") + + @JvmStatic + fun fromPrefValue(value: String?): DeviceProfile = + entries.firstOrNull { it.prefValue.equals(value, ignoreCase = true) } ?: DEFAULT + + @JvmStatic + fun detect(): DeviceProfile = + classify(buildHaystack(), marketingName()) + + internal fun classify( + buildHaystack: String, + marketingName: String, + ): DeviceProfile { + val name = marketingName.lowercase(Locale.ROOT) + val build = buildHaystack.lowercase(Locale.ROOT) + if (ASTRA_2_NAME_TOKENS.any { name.contains(it) }) return ASTRA_2 + if (ASTRA_2_BUILD_TOKENS.any { build.contains(it) }) return ASTRA_2 + if (ASTRA_2_NAME_TOKENS.any { build.contains(it) }) return ASTRA_2 + return DEFAULT + } + + @JvmStatic + fun deviceLabel(): String { + val marketing = marketingName().trim() + if (marketing.isNotEmpty()) return marketing + val model = Build.MODEL.orEmpty().trim() + val manufacturer = Build.MANUFACTURER.orEmpty().trim() + if (model.isEmpty()) return manufacturer + if (manufacturer.isEmpty()) return model + if (model.lowercase(Locale.ROOT).startsWith(manufacturer.lowercase(Locale.ROOT))) return model + return "$manufacturer $model" + } + + private fun buildHaystack(): String = + listOf( + Build.MANUFACTURER, + Build.BRAND, + Build.MODEL, + Build.DEVICE, + Build.PRODUCT, + Build.HARDWARE, + ).joinToString(" ") { it.orEmpty() } + + private fun marketingName(): String { + for (key in MARKETING_NAME_KEYS) { + val value = readProperty(key) + if (!value.isNullOrBlank()) return value + } + return "" + } + + private fun readProperty(key: String): String? = + try { + val clazz = Class.forName("android.os.SystemProperties") + (clazz.getMethod("get", String::class.java).invoke(null, key) as? String)?.ifEmpty { null } + } catch (e: Throwable) { + Log.w(TAG, "SystemProperties unavailable for $key", e) + null + } + } +} diff --git a/app/src/main/app/config/DeviceProfileSettings.kt b/app/src/main/app/config/DeviceProfileSettings.kt new file mode 100644 index 000000000..d0f0f1d68 --- /dev/null +++ b/app/src/main/app/config/DeviceProfileSettings.kt @@ -0,0 +1,97 @@ +package com.winlator.cmod.app.config + +import android.content.Context +import androidx.preference.PreferenceManager +import com.winlator.cmod.R + +enum class DeviceProfileFeature( + val titleRes: Int, + val summaryRes: Int, + val integrated: Boolean, +) { + INPUT_CONTROL_LAYOUTS( + R.string.device_profile_feature_input_layouts, + R.string.device_profile_feature_input_layouts_summary, + true, + ), + ADAPTIVE_JOYSTICKS( + R.string.device_profile_feature_adaptive_joysticks, + R.string.device_profile_feature_adaptive_joysticks_summary, + true, + ), + LIBRARY_ICONS( + R.string.device_profile_feature_library_icons, + R.string.device_profile_feature_library_icons_summary, + false, + ), + SESSION_MENU_SIZES( + R.string.device_profile_feature_session_menu, + R.string.device_profile_feature_session_menu_summary, + false, + ), + SHORTCUT_RECOMMENDATIONS( + R.string.device_profile_feature_shortcut_defaults, + R.string.device_profile_feature_shortcut_defaults_summary, + false, + ), +} + +object DeviceProfileSettings { + const val KEY_PROFILE = "device_profile" + const val KEY_DETECTED = "device_profile_detected" + + @Volatile + private var cached: DeviceProfile? = null + + private fun prefs(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) + + @JvmStatic + fun current(context: Context): DeviceProfile { + cached?.let { return it } + val resolved = DeviceProfile.fromPrefValue(prefs(context).getString(KEY_PROFILE, null)) + cached = resolved + return resolved + } + + @JvmStatic + fun setCurrent( + context: Context, + profile: DeviceProfile, + ) { + prefs(context).edit().putString(KEY_PROFILE, profile.prefValue).apply() + cached = profile + } + + @JvmStatic + fun detected(context: Context): DeviceProfile = + DeviceProfile.fromPrefValue(prefs(context).getString(KEY_DETECTED, null)) + + @JvmStatic + fun isUserOverridden(context: Context): Boolean = current(context) != detected(context) + + @JvmStatic + fun seedFromDetection(context: Context) { + val preferences = prefs(context) + val detected = DeviceProfile.detect() + val editor = preferences.edit().putString(KEY_DETECTED, detected.prefValue) + if (!preferences.contains(KEY_PROFILE)) { + editor.putString(KEY_PROFILE, detected.prefValue) + cached = detected + } + editor.apply() + } + + @JvmStatic + fun invalidate() { + cached = null + } + + @JvmStatic + fun assetProfilesToken(context: Context): String = current(context).assetToken + + @JvmStatic + fun adaptiveJoysticksDefault(context: Context): Boolean = current(context) == DeviceProfile.ASTRA_2 + + @JvmStatic + fun adaptiveJoysticksDefaultExtra(context: Context): String = if (adaptiveJoysticksDefault(context)) "1" else "0" +} diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp new file mode 100644 index 000000000..e15237bc8 --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp @@ -0,0 +1,265 @@ +{ + "id": 1, + "name": "RTS", + "cursorSpeed": 1, + "elements": [ + { + "type": "RANGE_BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.141667, + "y": 0.094086, + "toggleSwitch": false, + "text": "", + "iconId": 0, + "range": "FROM_F1_TO_F12" + }, + { + "type": "RANGE_BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.858333, + "y": 0.094086, + "toggleSwitch": false, + "text": "", + "iconId": 0, + "range": "FROM_1_TO_12" + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_ESC", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.104167, + "y": 0.376344, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_DEL", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.1875, + "y": 0.376344, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_TAB", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.104167, + "y": 0.504032, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_UP", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.1875, + "y": 0.504032, + "toggleSwitch": false, + "text": "", + "iconId": 3 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_LEFT", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.104167, + "y": 0.63172, + "toggleSwitch": false, + "text": "", + "iconId": 2 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_RIGHT", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.1875, + "y": 0.63172, + "toggleSwitch": false, + "text": "", + "iconId": 4 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_SHIFT_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.104167, + "y": 0.759409, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_DOWN", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.1875, + "y": 0.759409, + "toggleSwitch": false, + "text": "", + "iconId": 5 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_CTRL_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.104167, + "y": 0.887097, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_ALT_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.1875, + "y": 0.887097, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "MOUSE_RIGHT_BUTTON", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.820833, + "y": 0.658602, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_SPACE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.820833, + "y": 0.799731, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_BKSP", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.908333, + "y": 0.658602, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "SQUARE", + "bindings": [ + "KEY_ENTER", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.908333, + "y": 0.799731, + "toggleSwitch": false, + "text": "", + "iconId": 0 + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp new file mode 100644 index 000000000..14cd8ab14 --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -0,0 +1,215 @@ +{ + "id": 2, + "name": "Template (12 buttons)", + "cursorSpeed": 1, + "elements": [ + { + "type": "BUTTON", + "shape": "RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "D_PAD", + "shape": "CIRCLE", + "bindings": [ + "KEY_W", + "KEY_D", + "KEY_S", + "KEY_A" + ], + "scale": 1.05, + "x": 0.145833, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.420027, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.797917, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.935417, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.641801, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.902083, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.275, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.427083, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.572917, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 0 + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp new file mode 100644 index 000000000..030888f0d --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -0,0 +1,247 @@ +{ + "id": 3, + "name": "Virtual Gamepad", + "cursorSpeed": 1, + "elements": [ + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" + ], + "scale": 1.1, + "x": 0.145833, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "D_PAD", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" + ], + "scale": 0.95, + "x": 0.191667, + "y": 0.416667, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_RIGHT_THUMB_UP", + "GAMEPAD_RIGHT_THUMB_RIGHT", + "GAMEPAD_RIGHT_THUMB_DOWN", + "GAMEPAD_RIGHT_THUMB_LEFT" + ], + "scale": 1.0, + "x": 0.783333, + "y": 0.833333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_Y", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.420027, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_X", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.797917, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_B", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.935417, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_A", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.641801, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_L3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.902083, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_R3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.275, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_SELECT", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.427083, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 16 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_START", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.572917, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 15 + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp new file mode 100644 index 000000000..48e5f8350 --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -0,0 +1,281 @@ +{ + "id": 6, + "name": "FPS", + "cursorSpeed": 1.0000001, + "elements": [ + { + "type": "RANGE_BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.141667, + "y": 0.094086, + "toggleSwitch": false, + "text": "", + "iconId": 0, + "range": "FROM_F1_TO_F12" + }, + { + "type": "RANGE_BUTTON", + "shape": "CIRCLE", + "bindings": [ + "NONE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.858333, + "y": 0.094086, + "toggleSwitch": false, + "text": "", + "iconId": 0, + "range": "FROM_1_TO_12" + }, + { + "type": "RADIAL_MENU", + "shape": "CIRCLE", + "bindings": [ + "KEY_I", + "KEY_Z", + "KEY_X", + "KEY_M" + ], + "scale": 1.1, + "x": 0.083333, + "y": 0.349462, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "RADIAL_MENU", + "shape": "CIRCLE", + "bindings": [ + "KEY_F", + "KEY_G", + "KEY_C", + "KEY_T" + ], + "scale": 1.1, + "x": 0.933333, + "y": 0.336022, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "KEY_W", + "KEY_D", + "KEY_S", + "KEY_A" + ], + "scale": 1.1, + "x": 0.145833, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "D_PAD", + "shape": "CIRCLE", + "bindings": [ + "KEY_UP", + "KEY_RIGHT", + "KEY_DOWN", + "KEY_LEFT" + ], + "scale": 0.75, + "x": 0.191667, + "y": 0.416667, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_CTRL_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.125, + "y": 0.880376, + "toggleSwitch": true, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_SHIFT_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.220833, + "y": 0.880376, + "toggleSwitch": true, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_R", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.493952, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_E", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.797917, + "y": 0.604839, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "MOUSE_LEFT_BUTTON", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.935417, + "y": 0.604839, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_SPACE", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.715726, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "MOUSE_RIGHT_BUTTON", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.15, + "x": 0.783333, + "y": 0.833333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_ALT_L", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.902083, + "y": 0.840054, + "toggleSwitch": true, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "KEY_TAB", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.708333, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "KEY_ESC", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.427083, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "KEY_ENTER", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.572917, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 0 + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp new file mode 100644 index 000000000..f7cf2d69d --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -0,0 +1,247 @@ +{ + "id": 7, + "name": "GameHub", + "cursorSpeed": 1, + "elements": [ + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.25, + "x": 0.1, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.25, + "x": 0.9, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.25, + "x": 0.1, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.25, + "x": 0.9, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" + ], + "scale": 1.1, + "x": 0.145833, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "D_PAD", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" + ], + "scale": 1.0, + "x": 0.191667, + "y": 0.416667, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_RIGHT_THUMB_UP", + "GAMEPAD_RIGHT_THUMB_RIGHT", + "GAMEPAD_RIGHT_THUMB_DOWN", + "GAMEPAD_RIGHT_THUMB_LEFT" + ], + "scale": 1.0, + "x": 0.783333, + "y": 0.833333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_Y", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.05, + "x": 0.866667, + "y": 0.420027, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_X", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.05, + "x": 0.797917, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_B", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.05, + "x": 0.935417, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_A", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.05, + "x": 0.866667, + "y": 0.641801, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_L3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.902083, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_R3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.275, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_SELECT", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.427083, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 16 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_START", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.572917, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 15 + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp new file mode 100644 index 000000000..faac9978a --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -0,0 +1,231 @@ +{ + "id": 8, + "name": "No R-Stick", + "cursorSpeed": 1, + "elements": [ + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R2", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.087366, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_L1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.1, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_R1", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.2, + "x": 0.9, + "y": 0.208333, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "STICK", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" + ], + "scale": 1.1, + "x": 0.145833, + "y": 0.672043, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "D_PAD", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" + ], + "scale": 1.0, + "x": 0.2, + "y": 0.416667, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_Y", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.420027, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_X", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.797917, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_B", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.935417, + "y": 0.530914, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_A", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.866667, + "y": 0.641801, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_L3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.902083, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "CIRCLE", + "bindings": [ + "GAMEPAD_BUTTON_R3", + "NONE", + "NONE", + "NONE" + ], + "scale": 1.0, + "x": 0.275, + "y": 0.840054, + "toggleSwitch": false, + "text": "", + "iconId": 0 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_SELECT", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.427083, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 16 + }, + { + "type": "BUTTON", + "shape": "ROUND_RECT", + "bindings": [ + "GAMEPAD_BUTTON_START", + "NONE", + "NONE", + "NONE" + ], + "scale": 0.95, + "x": 0.572917, + "y": 0.893817, + "toggleSwitch": false, + "text": "", + "iconId": 15 + } + ] +} \ No newline at end of file diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index 95de2b86d..c66487b02 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatDialog import androidx.compose.foundation.layout.widthIn import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import com.winlator.cmod.app.config.DeviceProfileSettings import com.winlator.cmod.runtime.display.environment.components.NetworkingSettings import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE import com.winlator.cmod.shared.ui.nav.PaneNavWindowHandlers @@ -440,7 +441,10 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.enableDInput.value = true } state.adaptiveJoysticks.value = - c?.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, "0") == "1" + c?.getExtra( + InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, + DeviceProfileSettings.adaptiveJoysticksDefaultExtra(context), + ).let { it ?: DeviceProfileSettings.adaptiveJoysticksDefaultExtra(context) } == "1" state.fullscreenStretched.value = c?.isFullscreenStretched() ?: false state.useUnixLibs.value = c?.isUseUnixLibs() ?: true diff --git a/app/src/main/feature/settings/device/DeviceProfileScreen.kt b/app/src/main/feature/settings/device/DeviceProfileScreen.kt new file mode 100644 index 000000000..0ac88ead3 --- /dev/null +++ b/app/src/main/feature/settings/device/DeviceProfileScreen.kt @@ -0,0 +1,478 @@ +package com.winlator.cmod.feature.settings.device + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.ExpandLess +import androidx.compose.material.icons.outlined.ExpandMore +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.TabletAndroid +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.winlator.cmod.R +import com.winlator.cmod.app.config.DeviceProfile +import com.winlator.cmod.app.config.DeviceProfileFeature +import com.winlator.cmod.app.config.DeviceProfileSettings +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.runtime.input.controls.InputControlsManager +import com.winlator.cmod.shared.ui.focus.rememberSettingsContentNav +import com.winlator.cmod.shared.ui.layout.isPortraitLayout +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.paneNavItem + +private val PageBg = Color(0xFF101018) +private val PageText = Color(0xFFF0F4FF) +private val PageSub = Color(0xFF93A6BC) +private val PageCard = Color(0xFF181822) +private val PageAccent = Color(0xFF4FC3F7) +private val PageMuted = Color(0xFF5A6B80) + +@Composable +private fun SectionHeading( + textRes: Int, + descRes: Int, + topPadding: Int, +) { + Text( + stringResource(textRes), + color = PageSub, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp, + modifier = Modifier.padding(top = topPadding.dp), + ) + Text( + stringResource(descRes), + color = PageSub, + style = MaterialTheme.typography.labelMedium, + ) + Spacer(Modifier.size(2.dp)) +} + +@Composable +private fun DeviceHeaderCard( + profile: DeviceProfile, + deviceLabel: String, + overridden: Boolean, + expanded: Boolean, + onToggle: () -> Unit, +) { + val shape = RoundedCornerShape(14.dp) + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(shape) + .background(PageAccent.copy(alpha = 0.08f)) + .border(1.dp, PageAccent.copy(alpha = 0.35f), shape) + .clickable { onToggle() } + .paneNavItem( + cornerRadius = 14.dp, + onActivate = onToggle, + highlightColor = PageAccent, + tapToSelect = true, + ).padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(40.dp) + .clip(RoundedCornerShape(11.dp)) + .background(PageAccent.copy(alpha = 0.20f)), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.TabletAndroid, + contentDescription = null, + tint = PageAccent, + modifier = Modifier.size(22.dp), + ) + } + + Spacer(Modifier.width(14.dp)) + + Column(Modifier.weight(1f)) { + Text( + stringResource(profile.titleRes), + color = PageText, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + ) + Text( + deviceLabel, + color = PageSub, + fontSize = 11.sp, + ) + Text( + stringResource( + if (overridden) { + R.string.device_profile_source_manual + } else { + R.string.device_profile_source_detected + }, + ), + color = PageMuted, + fontSize = 11.sp, + ) + } + + Text( + stringResource(R.string.device_profile_change), + color = PageAccent, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 0.6.sp, + ) + Spacer(Modifier.width(6.dp)) + Icon( + if (expanded) Icons.Outlined.ExpandLess else Icons.Outlined.ExpandMore, + contentDescription = null, + tint = PageAccent, + modifier = Modifier.size(20.dp), + ) + } +} + +@Composable +private fun ProfileChoiceRow( + profile: DeviceProfile, + selected: Boolean, + onSelect: () -> Unit, +) { + val shape = RoundedCornerShape(12.dp) + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(shape) + .background(PageCard) + .clickable { onSelect() } + .paneNavItem( + cornerRadius = 12.dp, + onActivate = onSelect, + highlightColor = PageAccent, + tapToSelect = true, + ).padding(horizontal = 14.dp, vertical = 11.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text( + stringResource(profile.titleRes), + color = if (selected) PageAccent else PageText, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + ) + Text( + stringResource(profile.summaryRes), + color = PageSub, + fontSize = 11.sp, + ) + } + if (selected) { + Icon( + Icons.Outlined.Check, + contentDescription = null, + tint = PageAccent, + modifier = Modifier.size(20.dp), + ) + } + } +} + +@Composable +private fun FeatureRow( + feature: DeviceProfileFeature, + statusText: String?, + portrait: Boolean, +) { + val shape = RoundedCornerShape(12.dp) + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(shape) + .background(PageCard) + .padding(horizontal = 14.dp, vertical = 11.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text( + stringResource(feature.titleRes), + color = if (feature.integrated) PageText else PageText.copy(alpha = 0.45f), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + ) + Text( + stringResource(feature.summaryRes), + color = if (feature.integrated) PageSub else PageSub.copy(alpha = 0.45f), + fontSize = 11.sp, + ) + if (portrait) { + Spacer(Modifier.size(3.dp)) + FeatureStatus(feature, statusText) + } + } + if (!portrait) { + Spacer(Modifier.width(10.dp)) + FeatureStatus(feature, statusText) + } + } +} + +@Composable +private fun FeatureStatus( + feature: DeviceProfileFeature, + statusText: String?, +) { + if (feature.integrated) { + Text( + statusText.orEmpty(), + color = PageAccent, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + ) + } else { + Text( + stringResource(R.string.common_ui_coming_soon), + color = PageMuted, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 0.6.sp, + ) + } +} + +@Composable +private fun InfoRow( + labelRes: Int, + value: String, +) { + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(PageCard) + .padding(horizontal = 14.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + stringResource(labelRes), + color = PageSub, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f), + ) + Text( + value, + color = PageText, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + ) + } +} + +@Composable +private fun ActionRow( + titleRes: Int, + descRes: Int, + onActivate: () -> Unit, +) { + val shape = RoundedCornerShape(12.dp) + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(shape) + .background(PageCard) + .clickable { onActivate() } + .paneNavItem( + cornerRadius = 12.dp, + onActivate = onActivate, + highlightColor = PageAccent, + tapToSelect = true, + ).padding(horizontal = 14.dp, vertical = 11.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text( + stringResource(titleRes), + color = PageText, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + ) + Text( + stringResource(descRes), + color = PageSub, + fontSize = 11.sp, + ) + } + Icon( + Icons.Outlined.Refresh, + contentDescription = null, + tint = PageAccent, + modifier = Modifier.size(20.dp), + ) + } +} + +@Composable +fun DeviceProfileScreen(bridge: SettingsNavBridge? = null) { + val context = LocalContext.current + val contentNav = rememberSettingsContentNav(bridge) + val portrait = isPortraitLayout() + + var selected by remember { mutableStateOf(DeviceProfileSettings.current(context)) } + var expanded by remember { mutableStateOf(false) } + var resyncSignal by remember { mutableStateOf(0) } + + val deviceLabel = remember { DeviceProfile.deviceLabel() } + val overridden = selected != DeviceProfileSettings.detected(context) + + fun resyncLayouts() { + runCatching { InputControlsManager(context).resyncAssetProfiles() } + resyncSignal++ + } + + fun choose(profile: DeviceProfile) { + expanded = false + if (profile == selected) return + DeviceProfileSettings.setCurrent(context, profile) + selected = profile + resyncLayouts() + } + + val adaptiveStatus = + stringResource( + if (DeviceProfileSettings.adaptiveJoysticksDefault(context)) { + R.string.device_profile_recommended_on + } else { + R.string.device_profile_recommended_off + }, + ) + val layoutStatus = + stringResource( + if (selected == DeviceProfile.DEFAULT) { + R.string.device_profile_layouts_stock + } else { + R.string.device_profile_layouts_tuned + }, + ) + + CompositionLocalProvider(LocalPaneNav provides contentNav) { + Column( + modifier = + Modifier + .fillMaxSize() + .background(PageBg) + .verticalScroll(rememberScrollState()) + .windowInsetsPadding(WindowInsets.navigationBars) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SectionHeading( + textRes = R.string.device_profile_heading, + descRes = R.string.device_profile_desc, + topPadding = 4, + ) + + DeviceHeaderCard( + profile = selected, + deviceLabel = deviceLabel, + overridden = overridden, + expanded = expanded, + onToggle = { expanded = !expanded }, + ) + + if (expanded) { + DeviceProfile.entries.forEach { profile -> + ProfileChoiceRow( + profile = profile, + selected = profile == selected, + onSelect = { choose(profile) }, + ) + } + } + + SectionHeading( + textRes = R.string.device_profile_controls_heading, + descRes = R.string.device_profile_controls_desc, + topPadding = 10, + ) + + DeviceProfileFeature.entries.forEach { feature -> + FeatureRow( + feature = feature, + statusText = + when (feature) { + DeviceProfileFeature.INPUT_CONTROL_LAYOUTS -> layoutStatus + DeviceProfileFeature.ADAPTIVE_JOYSTICKS -> adaptiveStatus + else -> null + }, + portrait = portrait, + ) + } + + ActionRow( + titleRes = R.string.device_profile_reapply, + descRes = R.string.device_profile_reapply_desc, + onActivate = ::resyncLayouts, + ) + + SectionHeading( + textRes = R.string.device_profile_device_heading, + descRes = R.string.device_profile_device_desc, + topPadding = 10, + ) + + InfoRow(R.string.device_profile_info_model, android.os.Build.MODEL.orEmpty()) + InfoRow(R.string.device_profile_info_board, android.os.Build.DEVICE.orEmpty()) + InfoRow(R.string.device_profile_info_manufacturer, android.os.Build.MANUFACTURER.orEmpty()) + InfoRow( + R.string.device_profile_info_screen, + screenSummary(context), + ) + } + } +} + +private fun screenSummary(context: android.content.Context): String { + val metrics = context.resources.displayMetrics + val longEdge = maxOf(metrics.widthPixels, metrics.heightPixels) + val shortEdge = minOf(metrics.widthPixels, metrics.heightPixels) + return "$longEdge x $shortEdge" +} diff --git a/app/src/main/feature/settings/nav/SettingsNavGraph.kt b/app/src/main/feature/settings/nav/SettingsNavGraph.kt index a6c175d4c..80c1b1742 100644 --- a/app/src/main/feature/settings/nav/SettingsNavGraph.kt +++ b/app/src/main/feature/settings/nav/SettingsNavGraph.kt @@ -212,6 +212,9 @@ fun SettingsHost( popExitTransition = { fadeOut(tween(250, easing = androidx.compose.animation.core.FastOutSlowInEasing)) }, modifier = Modifier.fillMaxSize(), ) { + composable(SettingsRoutes.fromNavItem(SettingsNavItem.DEVICE_PROFILE)) { + com.winlator.cmod.feature.settings.device.DeviceProfileScreen(bridge = bridge) + } composable(SettingsRoutes.fromNavItem(SettingsNavItem.CONTAINERS)) { AndroidFragment() } diff --git a/app/src/main/feature/settings/nav/SettingsNavSidebar.kt b/app/src/main/feature/settings/nav/SettingsNavSidebar.kt index e504047c7..9666bf1aa 100644 --- a/app/src/main/feature/settings/nav/SettingsNavSidebar.kt +++ b/app/src/main/feature/settings/nav/SettingsNavSidebar.kt @@ -42,6 +42,7 @@ import androidx.compose.material.icons.outlined.Menu import androidx.compose.material.icons.outlined.Memory import androidx.compose.material.icons.outlined.ShoppingBag import androidx.compose.material.icons.outlined.SportsEsports +import androidx.compose.material.icons.outlined.TabletAndroid import androidx.compose.material.icons.outlined.Tune import androidx.compose.material.icons.outlined.VideogameAsset import androidx.compose.material.icons.outlined.ViewInAr @@ -95,6 +96,7 @@ private val InterFamily = FontFamily(Font(R.font.inter_medium, FontWeight.Medium // ─── Navigation model ─────────────────────────────────────────────── enum class NavSection { + DEVICE, ACCOUNTS, SYSTEM, TOOLS, @@ -108,6 +110,12 @@ enum class SettingsNavItem( val titleRes: Int, val section: NavSection, ) { + DEVICE_PROFILE( + R.id.main_menu_device_profile, + Icons.Outlined.TabletAndroid, + R.string.device_profile_nav_title, + NavSection.DEVICE, + ), GOOGLE(R.id.main_menu_google, Icons.Outlined.AccountCircle, R.string.google_cloud_google, NavSection.ACCOUNTS), STORES(R.id.main_menu_stores, Icons.Outlined.ShoppingBag, R.string.stores_accounts_title, NavSection.ACCOUNTS), CONTAINERS(R.id.main_menu_containers, Icons.Outlined.ViewInAr, R.string.common_ui_containers, NavSection.SYSTEM), diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 9602b1dbd..40bf1fc99 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.CoroutineScope +import com.winlator.cmod.app.config.DeviceProfileSettings import com.winlator.cmod.runtime.display.environment.components.NetworkingSettings import com.winlator.cmod.BuildConfig import com.winlator.cmod.R @@ -392,7 +393,10 @@ class ShortcutSettingsComposeDialog private constructor( state.disableXInput.value = shortcut.getExtra("disableXinput", "0") == "1" state.adaptiveJoysticks.value = getShortcutSetting( InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, - container.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, "0") + container.getExtra( + InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, + DeviceProfileSettings.adaptiveJoysticksDefaultExtra(context), + ), ) == "1" state.shortcutExclusiveXInput.value = shortcut.getExtra("exclusiveXInput", "").let { if (it.isEmpty()) container.isExclusiveXInput() else it == "1" @@ -1319,7 +1323,10 @@ class ShortcutSettingsComposeDialog private constructor( hasContainerOverride = hasContainerOverride or saveOverride( InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, if (state.adaptiveJoysticks.value) "1" else "0", - container.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, "0") + container.getExtra( + InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, + DeviceProfileSettings.adaptiveJoysticksDefaultExtra(context), + ), ) // Touchscreen mode @@ -2473,7 +2480,10 @@ class ShortcutSettingsComposeDialog private constructor( if ((inputType and WinHandler.FLAG_DINPUT_MAPPER_STANDARD.toInt()) == WinHandler.FLAG_DINPUT_MAPPER_STANDARD.toInt()) 0 else 1 state.shortcutExclusiveXInput.value = container.isExclusiveXInput() state.adaptiveJoysticks.value = - container.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, "0") == "1" + container.getExtra( + InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, + DeviceProfileSettings.adaptiveJoysticksDefaultExtra(context), + ) == "1" if (!state.shortcutExclusiveXInput.value) { state.enableXInput.value = true state.enableDInput.value = true diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index cc398cf49..3278cba91 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -179,6 +179,7 @@ Obtener arte + Ninguna biblioteca de tienda coincide con este juego Obteniendo… Acceso directo creado No se pudo crear el acceso directo: %s @@ -1081,6 +1082,42 @@ Por ejemplo, META para la tecla META, \n Escala del mouse Escala del mouse: %1$d%% + + Dispositivo + PERFIL DEL DISPOSITIVO + Un perfil por dispositivo, que ajusta los diseños y los tamaños a esta pantalla. + CAMBIAR + Detectado automáticamente + Elegido manualmente + Predeterminado + Todos los teléfonos y tablets sin un perfil propio. + Astra 2 + Tablet gamer RedMagic Astra 2, todos los modelos regionales. + LO QUE ESTABLECE ESTE PERFIL + Valores predeterminados que el perfil aplica en toda la app. + Diseños de controles de entrada + Diseños táctiles integrados ubicados para este tamaño de pantalla. + Palancas adaptables + Valor predeterminado para los contenedores que nunca lo han definido. + Tamaños y proporciones de los iconos de la biblioteca + Columnas de la cuadrícula y forma de las portadas en la biblioteca. + Tamaños de los botones del menú del juego + Tamaños de las áreas táctiles en el menú del juego. + Ajustes de juego recomendados + Valores predeterminados por juego sugeridos para este dispositivo. + Recomendado: Activado + Recomendado: Desactivado + Diseños originales + Ajustados a esta pantalla + Volver a aplicar los diseños integrados + Restaura todos los diseños integrados que no hayas editado. + ESTE DISPOSITIVO + Con qué se comparó el perfil. + Modelo + Placa + Fabricante + Pantalla + Velocidad del cursor Tema diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 4ad53e434..748a97418 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -178,6 +178,7 @@ Hent grafik + Ingen butiksbiblioteker matcher dette spil Henter Genvej oprettet Kunne ikke oprette genvej: %s @@ -794,6 +795,42 @@ F.eks. META for META-tast, \n Dødzone: %1$d%% Følsomhed: %1$d%% + + Enhed + ENHEDSPROFIL + Én profil pr. enhed, der tilpasser layouts og størrelser til denne skærm. + SKIFT + Fundet automatisk + Valgt manuelt + Standard + Alle telefoner og tablets uden deres egen profil. + Astra 2 + RedMagic Astra 2-gamingtablet, alle regionale modeller. + HVAD DENNE PROFIL SÆTTER + Standarder, som profilen anvender i hele appen. + Layouts til inputkontroller + Medfølgende touchlayouts placeret til denne skærmstørrelse. + Adaptive joysticks + Standard for containere, der aldrig har angivet den. + Ikonstørrelser og -forhold i biblioteket + Gitterkolonner og coverform i biblioteket. + Knapstørrelser i menuen i spillet + Størrelsen på berøringsmål i sessionspanelet. + Anbefalede spilindstillinger + Per-spil standarder foreslået til denne enhed. + Anbefalet: Til + Anbefalet: Fra + Standardlayouts + Tilpasset denne skærm + Anvend medfølgende layouts igen + Gendanner alle medfølgende layouts, du ikke har redigeret. + DENNE ENHED + Hvad profilen blev matchet mod. + Model + Hardwarekort + Producent + Skærm + Markørhastighed Tema diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ab2014838..ff05c41b6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -178,6 +178,7 @@ Artwork abrufen + Keine Store-Bibliothek passt zu diesem Spiel Wird abgerufen Verknüpfung erstellt Verknüpfung konnte nicht erstellt werden: %s @@ -794,6 +795,42 @@ Z. B. META für Meta-Taste, \n Totzone: %1$d%% Empfindlichkeit: %1$d%% + + Gerät + GERÄTEPROFIL + Ein Profil pro Gerät, das Layouts und Größen auf diesen Bildschirm abstimmt. + WECHSELN + Automatisch erkannt + Manuell gewählt + Standard + Jedes Smartphone und Tablet ohne eigenes Profil. + Astra 2 + RedMagic Astra 2 Gaming-Tablet, alle regionalen Modelle. + WAS DIESES PROFIL FESTLEGT + Standardwerte, die das Profil in der ganzen App anwendet. + Layouts der Eingabesteuerung + Mitgelieferte Touch-Layouts, passend zu dieser Bildschirmgröße platziert. + Adaptive Joysticks + Standard für Container, die es noch nie festgelegt haben. + Symbolgrößen und Seitenverhältnisse der Bibliothek + Rasterspalten und Coverform in der Bibliothek. + Tastengrößen im Spielmenü + Größe der Tippflächen im Sitzungsmenü. + Empfohlene Spieleinstellungen + Spielspezifische Standardwerte, die für dieses Gerät vorgeschlagen werden. + Empfohlen: Ein + Empfohlen: Aus + Standard-Layouts + Auf diesen Bildschirm abgestimmt + Mitgelieferte Layouts neu anwenden + Stellt jedes mitgelieferte Layout wieder her, das du nicht bearbeitet hast. + DIESES GERÄT + Womit das Profil abgeglichen wurde. + Modell + Platine + Hersteller + Bildschirm + Zeigergeschwindigkeit Design diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ab625ef90..e5ba83c40 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -178,6 +178,7 @@ Obtener arte + Ninguna biblioteca de tiendas coincide con este juego Obteniendo Acceso directo creado Error al crear acceso directo: %s @@ -794,6 +795,42 @@ Ej. META para la tecla META, \n Zona muerta: %1$d%% Sensibilidad: %1$d%% + + Dispositivo + PERFIL DEL DISPOSITIVO + Un perfil por dispositivo, que ajusta las disposiciones y los tamaños a esta pantalla. + CAMBIAR + Detectado automáticamente + Elegido manualmente + Predeterminado + Todos los móviles y tablets sin un perfil propio. + Astra 2 + Tablet gaming RedMagic Astra 2, todos los modelos regionales. + LO QUE DEFINE ESTE PERFIL + Valores predeterminados que el perfil aplica en toda la app. + Disposiciones de controles de entrada + Disposiciones táctiles integradas colocadas para este tamaño de pantalla. + Joysticks adaptativos + Predeterminado para los contenedores que nunca lo han configurado. + Tamaños y proporciones de los iconos de la biblioteca + Columnas de la cuadrícula y forma de las portadas en la biblioteca. + Tamaños de los botones del menú en el juego + Tamaños de las zonas táctiles del panel de sesión. + Ajustes de juego recomendados + Valores predeterminados por juego sugeridos para este dispositivo. + Recomendado: Activado + Recomendado: Desactivado + Disposiciones de serie + Ajustadas a esta pantalla + Reaplicar disposiciones integradas + Restaura todas las disposiciones integradas que no hayas editado. + ESTE DISPOSITIVO + Los datos con los que se emparejó el perfil. + Modelo + Placa + Fabricante + Pantalla + Velocidad del cursor Tema diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index a799c6b16..c1ea9b471 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -179,6 +179,7 @@ Hae kuvitus + Mikään kauppakirjasto ei vastaa tätä peliä Haetaan Pikakuvake luotu Pikakuvakkeen luonti epäonnistui: %s @@ -1081,6 +1082,42 @@ E.g. META for META-näppäin, \n Hiiren skaala Hiiren skaala: %1$d%% + + Laite + LAITEPROFIILI + Yksi profiili laitetta kohti: sovittaa asettelut ja koot tälle näytölle. + VAIHDA + Tunnistettu automaattisesti + Valittu itse + Oletus + Kaikki puhelimet ja tabletit, joilla ei ole omaa profiilia. + Astra 2 + RedMagic Astra 2 -pelitabletti, kaikki aluemallit. + MITÄ TÄMÄ PROFIILI MÄÄRITTÄÄ + Oletukset, joita profiili käyttää koko sovelluksessa. + Ohjainasettelut + Mukana tulevat kosketusasettelut tälle näyttökoolle. + Mukautuvat sauvat + Oletus säiliöille, joissa sitä ei ole koskaan asetettu. + Kirjaston kuvakekoot ja -suhteet + Ruudukon sarakkeet ja kansien muoto kirjastossa. + Pelinaikaisen valikon painikekoot + Kosketusalueiden koot istuntovalikossa. + Suositellut peliasetukset + Pelikohtaiset oletukset, joita suositellaan tälle laitteelle. + Suositus: Käytössä + Suositus: Pois + Vakioasettelut + Sovitettu tälle näytölle + Palauta vakioasettelut + Palauttaa kaikki mukana tulevat asettelut, joita et ole muokannut. + TÄMÄ LAITE + Mihin profiili sovitettiin. + Malli + Piirilevy + Valmistaja + Näyttö + Osoittimen nopeus Teema diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9044c9ea4..20becbde5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -178,6 +178,7 @@ Récupérer les illustrations + Aucune bibliothèque de boutique ne correspond à ce jeu Récupération Raccourci créé Échec de la création du raccourci : %s @@ -794,6 +795,42 @@ Par ex. META pour la touche META, \n Zone morte : %1$d%% Sensibilité : %1$d%% + + Appareil + PROFIL D\'APPAREIL + Un profil par appareil, adaptant les dispositions et les tailles à cet écran. + CHANGER + Détecté automatiquement + Choisi manuellement + Par défaut + Tous les téléphones et tablettes sans profil propre. + Astra 2 + Tablette de jeu RedMagic Astra 2, tous les modèles régionaux. + CE QUE CE PROFIL DÉFINIT + Valeurs par défaut que le profil applique dans toute l\'application. + Dispositions des contrôles de saisie + Dispositions tactiles intégrées placées pour cette taille d\'écran. + Joysticks adaptatifs + Valeur par défaut pour les conteneurs qui ne l\'ont jamais définie. + Tailles et ratios des icônes de la bibliothèque + Colonnes de la grille et forme des jaquettes dans la bibliothèque. + Tailles des boutons du menu en jeu + Tailles des zones tactiles dans le menu de session. + Paramètres de jeu recommandés + Valeurs par défaut par jeu suggérées pour cet appareil. + Recommandé : Activé + Recommandé : Désactivé + Dispositions d\'origine + Adaptées à cet écran + Réappliquer les dispositions intégrées + Restaure toutes les dispositions intégrées que vous n\'avez pas modifiées. + CET APPAREIL + Les critères utilisés pour associer le profil. + Modèle + Carte + Fabricant + Écran + Vitesse du curseur Thème diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index cd1f46abf..822166408 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -176,6 +176,7 @@ %d फ़ाइलें आर्टवर्क प्राप्त करें + इस गेम से कोई स्टोर लाइब्रेरी मेल नहीं खाती प्राप्त हो रहा है शॉर्टकट बनाया गया शॉर्टकट बनाने में विफल: %s @@ -2676,4 +2677,39 @@ प्राथमिक अडैप्टर के लिए बताया गया हार्डवेयर पता। डिवाइस से स्वतः बने पते के लिए इसे खाली छोड़ें। स्वतः: %1$s 12 हेक्स अंक दर्ज करें (उदाहरण के लिए 3c:5a:b4:12:34:56)। पहला बाइट यूनिकास्ट और वैश्विक रूप से प्रशासित होना चाहिए, यानी बिट 0 और 1 खाली हों; कई गेम स्थानीय रूप से प्रशासित पतों को अस्वीकार करते हैं, जैसे 02:…। + + डिवाइस + डिवाइस प्रोफ़ाइल + हर डिवाइस के लिए एक प्रोफ़ाइल, जो लेआउट और आकार इस स्क्रीन के अनुसार ढालती है। + बदलें + स्वतः पहचानी गई + मैन्युअल रूप से चुनी गई + डिफ़ॉल्ट + हर वह फ़ोन और टैबलेट जिसकी अपनी प्रोफ़ाइल नहीं है। + Astra 2 + RedMagic Astra 2 गेमिंग टैबलेट, हर क्षेत्रीय मॉडल। + यह प्रोफ़ाइल क्या सेट करती है + प्रोफ़ाइल पूरे ऐप में जो डिफ़ॉल्ट लागू करती है। + इनपुट नियंत्रण लेआउट + बंडल टच लेआउट, इस स्क्रीन आकार के अनुसार रखे गए। + अनुकूली जॉयस्टिक + उन कंटेनरों के लिए डिफ़ॉल्ट जिन्होंने इसे कभी सेट नहीं किया। + लाइब्रेरी आइकन आकार और अनुपात + लाइब्रेरी में ग्रिड कॉलम और कवर का आकार। + इन-गेम मेन्यू बटन आकार + सेशन ड्रॉअर में टच लक्ष्य के आकार। + अनुशंसित गेम सेटिंग्स + इस डिवाइस के लिए सुझाए गए प्रति-गेम डिफ़ॉल्ट। + अनुशंसित: चालू + अनुशंसित: बंद + मूल लेआउट + इस स्क्रीन के लिए ढाले गए + बंडल लेआउट फिर से लागू करें + हर उस बंडल लेआउट को पुनर्स्थापित करता है जिसे आपने संपादित नहीं किया है। + यह डिवाइस + प्रोफ़ाइल किससे मिलान करके चुनी गई। + मॉडल + बोर्ड + निर्माता + स्क्रीन diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a97a56e81..5ca663187 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -178,6 +178,7 @@ Recupera immagini + Nessuna libreria store corrisponde a questo gioco Recupero Scorciatoia creata Impossibile creare la scorciatoia: %s @@ -794,6 +795,42 @@ Ad es. META per il tasto META, \n Zona morta: %1$d%% Sensibilità: %1$d%% + + Dispositivo + PROFILO DISPOSITIVO + Un profilo per dispositivo, che adatta layout e dimensioni a questo schermo. + CAMBIA + Rilevato automaticamente + Scelto manualmente + Predefinito + Ogni telefono e tablet senza un profilo proprio. + Astra 2 + Tablet da gioco RedMagic Astra 2, tutti i modelli regionali. + COSA IMPOSTA QUESTO PROFILO + Valori predefiniti che il profilo applica in tutta l\'app. + Layout dei controlli + Layout touch integrati, posizionati per questa dimensione di schermo. + Levette adattive + Valore predefinito per i contenitori che non l\'hanno mai impostato. + Dimensioni e proporzioni delle icone + Colonne della griglia e forma delle copertine nella libreria. + Dimensioni dei pulsanti in gioco + Dimensioni delle aree touch nel menu di sessione. + Impostazioni di gioco consigliate + Valori predefiniti per gioco suggeriti per questo dispositivo. + Consigliato: attivo + Consigliato: disattivo + Layout standard + Adattati a questo schermo + Riapplica i layout integrati + Ripristina ogni layout integrato che non hai modificato. + QUESTO DISPOSITIVO + I dati con cui è stato abbinato il profilo. + Modello + Scheda + Produttore + Schermo + Velocità cursore Tema diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index dfd9a7323..9ca5b6b9f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -179,6 +179,7 @@ アートワークを取得 + このゲームに一致するストアライブラリはありません 取得中 ショートカットを作成しました ショートカットの作成に失敗しました: %s @@ -1081,6 +1082,42 @@ マウススケール マウススケール: %1$d%% + + デバイス + デバイスプロファイル + デバイスごとに 1 つのプロファイルで、レイアウトとサイズをこの画面に合わせます。 + 変更 + 自動的に検出されました + 手動で選択されました + 既定 + 専用プロファイルがないすべてのスマートフォンとタブレット。 + Astra 2 + RedMagic Astra 2 ゲーミングタブレット、全地域モデル。 + このプロファイルが設定する項目 + プロファイルがアプリ全体に適用する既定値。 + 入力コントロールのレイアウト + この画面サイズに合わせて配置された組み込みタッチレイアウト。 + アダプティブジョイスティック + 未設定のコンテナに適用される既定値。 + ライブラリのアイコンサイズと比率 + ライブラリのグリッド列数とカバーの形状。 + ゲーム内メニューボタンのサイズ + セッションドロワーのタッチ領域のサイズ。 + 推奨のゲーム設定 + このデバイスに推奨されるゲーム個別の既定値。 + 推奨: オン + 推奨: オフ + 標準レイアウト + この画面に合わせて調整 + 組み込みレイアウトを再適用 + 編集していない組み込みレイアウトをすべて元に戻します。 + このデバイス + プロファイルの判定に使用された情報。 + モデル + ボード + メーカー + 画面 + カーソル速度 テーマ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index ab047ef8e..eaf5cf438 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -178,6 +178,7 @@ 아트워크 가져오기 + 이 게임과 일치하는 스토어 라이브러리가 없습니다 가져오는 중 바로가기가 생성되었습니다 바로가기 생성 실패: %s @@ -794,6 +795,42 @@ 데드존: %1$d%% 감도: %1$d%% + + 기기 + 기기 프로필 + 기기마다 하나의 프로필로, 레이아웃과 크기를 이 화면에 맞게 조정합니다. + 변경 + 자동으로 감지됨 + 수동으로 선택됨 + 기본값 + 전용 프로필이 없는 모든 휴대폰과 태블릿. + Astra 2 + RedMagic Astra 2 게이밍 태블릿, 모든 지역 모델. + 이 프로필이 설정하는 항목 + 프로필이 앱 전체에 적용하는 기본값입니다. + 입력 컨트롤 레이아웃 + 이 화면 크기에 맞게 배치된 기본 제공 터치 레이아웃. + 적응형 조이스틱 + 한 번도 설정하지 않은 컨테이너의 기본값. + 라이브러리 아이콘 크기 및 비율 + 라이브러리의 그리드 열 수와 커버 모양. + 게임 내 메뉴 버튼 크기 + 세션 드로어의 터치 영역 크기. + 권장 게임 설정 + 이 기기에 제안되는 게임별 기본값. + 권장: 켜기 + 권장: 끄기 + 기본 레이아웃 + 이 화면에 맞게 조정됨 + 기본 제공 레이아웃 다시 적용 + 편집하지 않은 모든 기본 제공 레이아웃을 복원합니다. + 이 기기 + 프로필이 일치한 기준입니다. + 모델 + 보드 + 제조사 + 화면 + 커서 속도 테마 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 647cd8ed1..8135f6fb0 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -179,6 +179,7 @@ Hent grafikk + Ingen butikkbibliotek passer til dette spillet Henter Snarvei opprettet Kunne ikke opprette snarvei: %s @@ -1081,6 +1082,42 @@ F.eks. META for META-tast, \n Musskala Musskala: %1$d%% + + Enhet + ENHETSPROFIL + Én profil per enhet som tilpasser oppsett og størrelser til denne skjermen. + BYTT + Oppdaget automatisk + Valgt manuelt + Standard + Alle telefoner og nettbrett uten egen profil. + Astra 2 + RedMagic Astra 2-spillnettbrett, alle regionale modeller. + HVA DENNE PROFILEN ANGIR + Standardverdiene profilen bruker i hele appen. + Kontrolloppsett for inndata + Medfølgende berøringsoppsett plassert for denne skjermstørrelsen. + Adaptive styrespaker + Standard for beholdere som aldri har angitt det. + Ikonstørrelser og format i biblioteket + Rutenettkolonner og omslagsform i biblioteket. + Knappestørrelser i menyen i spillet + Størrelsen på berøringsmål i menyen i spillet. + Anbefalte spillinnstillinger + Standardverdier per spill som foreslås for denne enheten. + Anbefalt: På + Anbefalt: Av + Standardoppsett + Tilpasset denne skjermen + Bruk medfølgende oppsett på nytt + Gjenoppretter alle medfølgende oppsett du ikke har redigert. + DENNE ENHETEN + Det profilen ble matchet mot. + Modell + Hovedkort + Produsent + Skjerm + Markørhastighet Tema diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a358f6da8..18f33ff28 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -182,6 +182,7 @@ Pobierz grafiki + Żadna biblioteka sklepu nie zawiera tej gry Pobieranie Skrót utworzony Nie udało się utworzyć skrótu: %s @@ -800,6 +801,42 @@ Np. META dla klawisza META, \n Martwa strefa: %1$d%% Czułość: %1$d%% + + Urządzenie + PROFIL URZĄDZENIA + Jeden profil na urządzenie, dostosowujący układy i rozmiary do tego ekranu. + ZMIEŃ + Wykryto automatycznie + Wybrano ręcznie + Domyślny + Każdy telefon i tablet bez własnego profilu. + Astra 2 + Tablet do gier RedMagic Astra 2, każdy model regionalny. + CO USTAWIA TEN PROFIL + Wartości domyślne stosowane przez profil w całej aplikacji. + Układy kontrolek wejścia + Dołączone układy dotykowe rozmieszczone dla tego rozmiaru ekranu. + Adaptacyjne drążki + Domyślne dla kontenerów, w których nigdy tego nie ustawiono. + Rozmiary i proporcje ikon biblioteki + Kolumny siatki i kształt okładek w bibliotece. + Rozmiary przycisków menu w grze + Rozmiary obszarów dotyku w menu sesji. + Zalecane ustawienia gry + Ustawienia domyślne dla gier sugerowane dla tego urządzenia. + Zalecane: Włączone + Zalecane: Wyłączone + Układy standardowe + Dostosowane do tego ekranu + Zastosuj ponownie dołączone układy + Przywraca każdy dołączony układ, którego nie edytowałeś. + TO URZĄDZENIE + Do czego dopasowano profil. + Model + Płyta + Producent + Ekran + Prędkość kursora Motyw diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 09a599e76..e685df04d 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -178,6 +178,7 @@ Obter arte + Nenhuma biblioteca de loja corresponde a este jogo Obtendo… Atalho criado Falha ao criar atalho: %s @@ -794,6 +795,42 @@ Ex. META para tecla META, \n Zona Morta: %1$d%% Sensibilidade: %1$d%% + + Dispositivo + PERFIL DO DISPOSITIVO + Um perfil por dispositivo, ajustando layouts e tamanhos a esta tela. + MUDAR + Detectado automaticamente + Escolhido manualmente + Padrão + Todo celular e tablet sem um perfil próprio. + Astra 2 + Tablet gamer RedMagic Astra 2, todos os modelos regionais. + O QUE ESTE PERFIL DEFINE + Padrões que o perfil aplica em todo o aplicativo. + Layouts de controles + Layouts de toque inclusos, posicionados para este tamanho de tela. + Analógicos adaptáveis + Padrão para containers que nunca definiram isso. + Tamanhos e proporções dos ícones da biblioteca + Colunas da grade e formato das capas na biblioteca. + Tamanho dos botões do menu no jogo + Tamanho das áreas de toque na gaveta da sessão. + Configurações recomendadas do jogo + Padrões por jogo sugeridos para este dispositivo. + Recomendado: Ativado + Recomendado: Desativado + Layouts originais + Ajustados para esta tela + Reaplicar layouts inclusos + Restaura todo layout incluso que você não editou. + ESTE DISPOSITIVO + Com o que o perfil foi comparado. + Modelo + Placa + Fabricante + Tela + Velocidade do Cursor Tema diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 77ed1ece3..e63793917 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -179,6 +179,7 @@ Obter arte + Nenhuma biblioteca de loja corresponde a este jogo A obter… Atalho criado Falha ao criar atalho: %s @@ -1081,6 +1082,42 @@ Por ex. META para tecla META, \n Escala do rato Escala do rato: %1$d%% + + Dispositivo + PERFIL DO DISPOSITIVO + Um perfil por dispositivo, a ajustar disposições e tamanhos a este ecrã. + ALTERAR + Detetado automaticamente + Escolhido manualmente + Predefinição + Todos os telemóveis e tablets sem perfil próprio. + Astra 2 + Tablet de jogos RedMagic Astra 2, todos os modelos regionais. + O QUE ESTE PERFIL DEFINE + Predefinições que o perfil aplica em toda a aplicação. + Disposições dos controlos de entrada + Disposições táteis incluídas, posicionadas para este tamanho de ecrã. + Analógicos adaptativos + Predefinição para contentores que nunca a definiram. + Tamanhos e proporções dos ícones da biblioteca + Colunas da grelha e forma das capas na biblioteca. + Tamanhos dos botões do menu no jogo + Tamanhos das áreas de toque no menu da sessão. + Definições de jogo recomendadas + Predefinições por jogo sugeridas para este dispositivo. + Recomendado: Ligado + Recomendado: Desligado + Disposições originais + Ajustadas a este ecrã + Reaplicar disposições incluídas + Restaura todas as disposições incluídas que não editou. + ESTE DISPOSITIVO + Aquilo com que o perfil foi comparado. + Modelo + Placa + Fabricante + Ecrã + Velocidade do cursor Tema diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 322ad3094..0c167cc9e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -178,6 +178,7 @@ Obține ilustrații + Nicio bibliotecă de magazin nu se potrivește cu acest joc Se obțin… Comanda rapida creata Nu s-a putut crea comanda rapida: %s @@ -794,6 +795,42 @@ De ex. META pentru tasta META, \n Zona moarta: %1$d%% Sensibilitate: %1$d%% + + Dispozitiv + PROFIL DISPOZITIV + Un profil pentru fiecare dispozitiv, care adaptează aspectele și dimensiunile la acest ecran. + SCHIMBĂ + Detectat automat + Ales manual + Implicit + Toate telefoanele și tabletele fără un profil propriu. + Astra 2 + Tableta de gaming RedMagic Astra 2, toate modelele regionale. + CE SETEAZĂ ACEST PROFIL + Valorile implicite pe care profilul le aplică în toată aplicația. + Aspecte ale comenzilor + Aspectele tactile incluse, poziționate pentru această dimensiune de ecran. + Joystick-uri adaptive + Valoarea implicită pentru containerele care nu au setat-o niciodată. + Dimensiuni și rapoarte ale pictogramelor + Coloanele grilei și forma copertelor în bibliotecă. + Dimensiuni ale butoanelor din meniul de joc + Dimensiunea zonelor tactile din meniul de sesiune. + Setări de joc recomandate + Valori implicite per joc, sugerate pentru acest dispozitiv. + Recomandat: Activat + Recomandat: Dezactivat + Aspecte standard + Adaptate pentru acest ecran + Reaplică aspectele incluse + Restaurează fiecare aspect inclus pe care nu l-ai modificat. + ACEST DISPOZITIV + Elementele după care a fost potrivit profilul. + Model + Placă + Producător + Ecran + Viteza cursorului Tema diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 30ba961dc..793016d1a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -181,6 +181,7 @@ Загрузить обложки + Этой игре не соответствует ни одна библиотека магазина Загрузка Ярлык создан. Не удалось создать ярлык: %s @@ -866,6 +867,42 @@ Чувствительность: %1$d%% Масштаб мыши Масштаб мыши: %1$d%% + + Устройство + ПРОФИЛЬ УСТРОЙСТВА + Один профиль на устройство: макеты и размеры подгоняются под этот экран. + ИЗМЕНИТЬ + Определено автоматически + Выбрано вручную + По умолчанию + Все телефоны и планшеты без собственного профиля. + Astra 2 + Игровой планшет RedMagic Astra 2, все региональные модели. + ЧТО ЗАДАЁТ ЭТОТ ПРОФИЛЬ + Значения по умолчанию, которые профиль применяет во всём приложении. + Макеты элементов управления + Встроенные сенсорные макеты, размещённые под этот размер экрана. + Адаптивные стики + Значение по умолчанию для контейнеров, где оно ещё не задавалось. + Размеры и пропорции значков библиотеки + Столбцы сетки и форма обложек в библиотеке. + Размеры кнопок игрового меню + Размеры зон касания в игровой панели. + Рекомендуемые настройки игр + Настройки по умолчанию, предлагаемые для этого устройства. + Рекомендуется: Вкл. + Рекомендуется: Выкл. + Исходные макеты + Подогнано под этот экран + Применить встроенные макеты заново + Восстанавливает все встроенные макеты, которые вы не изменяли. + ЭТО УСТРОЙСТВО + С чем сопоставлялся профиль. + Модель + Плата + Производитель + Экран + Скорость курсора Тема diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 7ea33c36b..99ae2cd36 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -179,6 +179,7 @@ Hämta grafik + Inga butiksbibliotek matchar det här spelet Hämtar Genväg skapad Det gick inte att skapa genväg: %s @@ -1081,6 +1082,42 @@ T.ex. META för META-tangent, \n Musskala Musskala: %1$d%% + + Enhet + ENHETSPROFIL + En profil per enhet som anpassar layouter och storlekar till den här skärmen. + BYT + Upptäckt automatiskt + Vald manuellt + Standard + Alla telefoner och tabletter utan en egen profil. + Astra 2 + Speltabletten RedMagic Astra 2, alla regionala modeller. + VAD DEN HÄR PROFILEN STÄLLER IN + Standardvärden som profilen tillämpar i hela appen. + Layouter för inmatningskontroller + Medföljande pekkontrollayouter placerade för den här skärmstorleken. + Adaptiva styrspakar + Standard för containrar som aldrig ställt in det. + Ikonstorlekar och proportioner i biblioteket + Rutnätskolumner och omslagsform i biblioteket. + Knappstorlekar i spelmenyn + Storlek på tryckytorna i sessionsmenyn. + Rekommenderade spelinställningar + Standardvärden per spel som föreslås för den här enheten. + Rekommenderat: På + Rekommenderat: Av + Standardlayouter + Anpassade för den här skärmen + Tillämpa medföljande layouter igen + Återställer alla medföljande layouter som du inte har redigerat. + DEN HÄR ENHETEN + Vad profilen matchades mot. + Modell + Kretskort + Tillverkare + Skärm + Markörhastighet Tema diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index fec8fe2a2..0463b4111 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -179,6 +179,7 @@ ดึงภาพประกอบ + ไม่มีคลังร้านค้าที่ตรงกับเกมนี้ กำลังดึง สร้างทางลัดแล้ว สร้างทางลัดไม่สำเร็จ: %s @@ -1081,6 +1082,42 @@ สเกลเมาส์ สเกลเมาส์: %1$d%% + + อุปกรณ์ + โปรไฟล์อุปกรณ์ + หนึ่งโปรไฟล์ต่อหนึ่งอุปกรณ์ ปรับเลย์เอาต์และขนาดให้เข้ากับหน้าจอนี้ + เปลี่ยน + ตรวจพบอัตโนมัติ + เลือกด้วยตนเอง + ค่าเริ่มต้น + โทรศัพท์และแท็บเล็ตทุกเครื่องที่ไม่มีโปรไฟล์ของตัวเอง + Astra 2 + แท็บเล็ตเกมมิ่ง RedMagic Astra 2 ทุกรุ่นในทุกภูมิภาค + สิ่งที่โปรไฟล์นี้กำหนด + ค่าเริ่มต้นที่โปรไฟล์ใช้กับทั้งแอป + เลย์เอาต์ปุ่มควบคุมอินพุต + เลย์เอาต์สัมผัสในตัว จัดวางให้พอดีกับขนาดหน้าจอนี้ + จอยสติกแบบปรับอัตโนมัติ + ค่าเริ่มต้นสำหรับคอนเทนเนอร์ที่ยังไม่เคยตั้งค่านี้ + ขนาดและสัดส่วนไอคอนในคลัง + จำนวนคอลัมน์และรูปทรงภาพปกในคลัง + ขนาดปุ่มเมนูในเกม + ขนาดพื้นที่สัมผัสในเมนูในเกม + การตั้งค่าเกมที่แนะนำ + ค่าเริ่มต้นเฉพาะเกมที่แนะนำสำหรับอุปกรณ์นี้ + แนะนำ: เปิด + แนะนำ: ปิด + เลย์เอาต์ต้นฉบับ + ปรับให้เข้ากับหน้าจอนี้ + ใช้เลย์เอาต์ในตัวอีกครั้ง + คืนค่าเลย์เอาต์ในตัวทุกชุดที่คุณยังไม่ได้แก้ไข + อุปกรณ์นี้ + ข้อมูลที่ใช้จับคู่กับโปรไฟล์ + รุ่น + บอร์ด + ผู้ผลิต + หน้าจอ + ความเร็วเคอร์เซอร์ ธีม diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2a7ad1095..460f3eef8 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -179,6 +179,7 @@ Görselleri Getir + Bu oyunla eşleşen mağaza kütüphanesi yok Getiriliyor Kısayol oluşturuldu Kısayol oluşturulamadı: %s @@ -1081,6 +1082,42 @@ E.g. META için META tuşu, \n Fare Ölçeği Fare Ölçeği: %1$d%% + + Cihaz + CİHAZ PROFİLİ + Her cihaz için tek profil; düzenleri ve boyutları bu ekrana göre ayarlar. + DEĞİŞTİR + Otomatik algılandı + Elle seçildi + Varsayılan + Kendi profili olmayan tüm telefon ve tabletler. + Astra 2 + RedMagic Astra 2 oyun tableti, tüm bölgesel modeller. + BU PROFİLİN AYARLADIKLARI + Profilin uygulama genelinde uyguladığı varsayılanlar. + Giriş kontrol düzenleri + Bu ekran boyutuna göre yerleştirilmiş yerleşik dokunmatik düzenler. + Uyarlanabilir analog çubuklar + Bunu hiç ayarlamamış konteynerler için varsayılan. + Kütüphane simge boyutları ve oranları + Kütüphanedeki ızgara sütunları ve kapak şekli. + Oyun içi menü düğme boyutları + Oturum çekmecesindeki dokunma hedefi boyutları. + Önerilen oyun ayarları + Bu cihaz için önerilen oyun başına varsayılanlar. + Önerilen: Açık + Önerilen: Kapalı + Standart düzenler + Bu ekrana göre ayarlandı + Yerleşik düzenleri yeniden uygula + Düzenlemediğiniz tüm yerleşik düzenleri geri yükler. + BU CİHAZ + Profilin eşleştirildiği bilgiler. + Model + Kart + Üretici + Ekran + İmleç Hızı Tema diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ef3f9a528..d0f5d0b59 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -182,6 +182,7 @@ Завантажити зображення + Жодна бібліотека магазину не містить цієї гри Завантаження Ярлик створено Не вдалося створити ярлик: %s @@ -800,6 +801,42 @@ Мертва зона: %1$d%% Чутливість: %1$d%% + + Пристрій + ПРОФІЛЬ ПРИСТРОЮ + Один профіль на пристрій — підлаштовує макети та розміри під цей екран. + ЗМІНИТИ + Визначено автоматично + Вибрано вручну + Типовий + Усі телефони та планшети без власного профілю. + Astra 2 + Ігровий планшет RedMagic Astra 2, усі регіональні моделі. + ЩО ЗАДАЄ ЦЕЙ ПРОФІЛЬ + Типові значення, які профіль застосовує в усьому застосунку. + Макети керування + Вбудовані сенсорні макети, розміщені під цей розмір екрана. + Адаптивні джойстики + Типове значення для контейнерів, де його ніколи не задавали. + Розміри та пропорції значків бібліотеки + Стовпці сітки та форма обкладинок у бібліотеці. + Розміри кнопок меню в грі + Розміри зон торкання в меню сесії. + Рекомендовані налаштування гри + Типові значення для кожної гри, запропоновані для цього пристрою. + Рекомендовано: увімк. + Рекомендовано: вимк. + Стандартні макети + Підлаштовано під цей екран + Застосувати вбудовані макети знову + Відновлює всі вбудовані макети, які ви не редагували. + ЦЕЙ ПРИСТРІЙ + З чим було зіставлено профіль. + Модель + Плата + Виробник + Екран + Швидкість курсора Тема diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3c42fc0ef..cde0a6e31 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -178,6 +178,7 @@ 抓取封面 + 没有商店库与此游戏匹配 抓取中 快捷方式已创建 创建快捷方式失败:%s @@ -794,6 +795,42 @@ 死区:%1$d%% 灵敏度:%1$d%% + + 设备 + 设备配置文件 + 每台设备一个配置文件,按此屏幕调整布局和尺寸。 + 更换 + 自动检测 + 手动选择 + 默认 + 适用于所有没有专属配置文件的手机和平板。 + Astra 2 + RedMagic Astra 2 游戏平板,涵盖所有地区版本。 + 此配置文件的设定内容 + 配置文件在整个应用中应用的默认值。 + 输入控制布局 + 内置触摸布局按此屏幕尺寸摆放。 + 自适应摇杆 + 从未设置过此项的容器所用的默认值。 + 游戏库图标大小和比例 + 游戏库中的网格列数和封面形状。 + 游戏内菜单按钮大小 + 会话抽屉中的触摸区域大小。 + 推荐游戏设置 + 为此设备建议的单游戏默认值。 + 推荐:开 + 推荐:关 + 原始布局 + 已按此屏幕调整 + 重新应用内置布局 + 恢复所有你未编辑过的内置布局。 + 本设备 + 配置文件的匹配依据。 + 型号 + 主板 + 制造商 + 屏幕 + 光标速度 主题 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c145c526d..4457ba740 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -178,6 +178,7 @@ 擷取美術圖 + 沒有商店遊戲庫符合此遊戲 擷取中 捷徑已建立 無法建立捷徑:%s @@ -794,6 +795,42 @@ 死區:%1$d%% 靈敏度:%1$d%% + + 裝置 + 裝置設定檔 + 每部裝置一個設定檔,依此螢幕調整版面配置與尺寸。 + 變更 + 自動偵測 + 手動選擇 + 預設 + 所有沒有專屬設定檔的手機與平板。 + Astra 2 + RedMagic Astra 2 遊戲平板,涵蓋所有地區型號。 + 此設定檔會設定什麼 + 設定檔在整個應用程式中套用的預設值。 + 輸入控制版面配置 + 依此螢幕尺寸擺放的內建觸控版面配置。 + 自適應搖桿 + 從未設定過此項的容器所使用的預設值。 + 遊戲庫圖示尺寸與比例 + 遊戲庫中的格線欄數與封面形狀。 + 遊戲內選單按鈕尺寸 + 工作階段抽屜中的觸控目標尺寸。 + 建議的遊戲設定 + 為此裝置建議的個別遊戲預設值。 + 建議:開啟 + 建議:關閉 + 原始版面配置 + 已針對此螢幕調整 + 重新套用內建版面配置 + 還原你未曾編輯過的所有內建版面配置。 + 此裝置 + 設定檔比對時所依據的資訊。 + 型號 + 主板 + 製造商 + 螢幕 + 游標速度 佈景主題 diff --git a/app/src/main/res/values/refs.xml b/app/src/main/res/values/refs.xml index 587d87110..dd8186d2d 100644 --- a/app/src/main/res/values/refs.xml +++ b/app/src/main/res/values/refs.xml @@ -1,6 +1,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72bc6a56b..07b889d55 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1230,6 +1230,42 @@ E.g. META for META key, \n Mouse Scale Mouse Scale: %1$d%% + + Device + DEVICE PROFILE + One profile per device, tuning layouts and sizes to this screen. + CHANGE + Detected automatically + Chosen manually + Default + Every phone and tablet without a profile of its own. + Astra 2 + RedMagic Astra 2 gaming tablet, every regional model. + WHAT THIS PROFILE SETS + Defaults the profile applies across the app. + Input control layouts + Bundled touch layouts placed for this screen size. + Adaptive joysticks + Default for containers that have never set it. + Library icon sizes and ratios + Grid columns and cover shape in the library. + In-game menu button sizes + Touch target sizes in the session drawer. + Recommended game settings + Per-game defaults suggested for this device. + Recommended: On + Recommended: Off + Stock layouts + Tuned for this screen + Re-apply bundled layouts + Restores every bundled layout you have not edited. + THIS DEVICE + What the profile was matched against. + Model + Board + Manufacturer + Screen + Cursor Speed Theme diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index e2052ed95..9a2d39da2 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -48,6 +48,7 @@ import androidx.core.content.FileProvider; import androidx.compose.ui.platform.ComposeView; import androidx.core.view.WindowInsetsCompat; +import com.winlator.cmod.app.config.DeviceProfileSettings; import com.winlator.cmod.BuildConfig; import com.winlator.cmod.feature.leaderboard.SessionRecordingController; import com.winlator.cmod.feature.stores.steam.enums.Marker; @@ -777,7 +778,10 @@ private String getShortcutSetting(String key, String containerValue) { } private String containerAdaptiveJoysticks() { - return container != null ? container.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, "0") : "0"; + String fallback = DeviceProfileSettings.adaptiveJoysticksDefaultExtra(this); + return container != null + ? container.getExtra(InputControlsView.EXTRA_ADAPTIVE_JOYSTICKS, fallback) + : fallback; } private boolean isAdaptiveJoysticksEnabled() { diff --git a/app/src/main/runtime/input/controls/ControlElement.java b/app/src/main/runtime/input/controls/ControlElement.java index eacbc44bc..3f5129a8e 100644 --- a/app/src/main/runtime/input/controls/ControlElement.java +++ b/app/src/main/runtime/input/controls/ControlElement.java @@ -103,6 +103,7 @@ public static String[] names() { private boolean isRadialBindingCurrentlyHeld = false; private boolean wasExpandedOnDown = false; private int currentPointerId = -1; + private int pressGeneration = 0; private boolean adaptiveShifted = false; private final Rect boundingBox = new Rect(); private final Path path = new Path(); @@ -3851,6 +3852,7 @@ public boolean handleTouchDown(int pointerId, float x, float y) { currentPointerId = pointerId; if (isAdaptiveStick()) shiftAdaptiveOrigin(x, y); if (type == Type.BUTTON) { + pressGeneration++; if (isKeepButtonPressedAfterMinTime()) touchTime = System.currentTimeMillis(); if (!toggleSwitch || !selected) { dispatchButtonBinding(true); @@ -3903,9 +3905,6 @@ public boolean handleTouchDown(int pointerId, float x, float y) { public boolean handleTouchMove(int pointerId, float x, float y) { if (pointerId == currentPointerId && type == Type.BUTTON) { - if (!containsPoint(x, y)) { - handleTouchUp(pointerId, x, y); - } return true; } @@ -4111,8 +4110,10 @@ public boolean handleTouchUp(int pointerId, float x, float y) { if (isKeepButtonPressedAfterMinTime() && touchTime != null) { long held = System.currentTimeMillis() - (long) touchTime; long delay = Math.max(0L, BUTTON_MIN_TIME_TO_KEEP_PRESSED - held); + final int generation = ++pressGeneration; inputControlsView.postDelayed( () -> { + if (generation != pressGeneration) return; dispatchButtonBinding(false); inputControlsView.invalidate(); }, diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index d299794b3..13ae2a574 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -8,6 +8,7 @@ import android.util.JsonReader; import android.util.Log; import androidx.preference.PreferenceManager; +import com.winlator.cmod.app.config.DeviceProfileSettings; import com.winlator.cmod.app.config.SettingsConfig; import com.winlator.cmod.shared.android.AppUtils; import com.winlator.cmod.shared.io.FileUtils; @@ -25,7 +26,8 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 8; + private static final int ASSET_PROFILE_SYNC_REVISION = 9; + private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; public static final int GAMEHUB_LAYOUT_BUILTIN_ID = 7; @@ -107,6 +109,35 @@ public ArrayList getProfiles(boolean ignoreTemplates) { return profiles; } + private static String assetProfilesDir(String deviceToken) { + if (deviceToken == null || deviceToken.isEmpty()) return ASSET_PROFILES_DIR; + return ASSET_PROFILES_DIR + "-" + deviceToken; + } + + private static String[] listAssetProfiles(AssetManager assetManager, String dir) { + String[] names; + try { + names = assetManager.list(dir); + } catch (IOException e) { + return null; + } + if (names == null) return null; + ArrayList icps = new ArrayList<>(); + for (String name : names) if (name.toLowerCase(Locale.ROOT).endsWith(".icp")) icps.add(name); + return icps.isEmpty() ? null : icps.toArray(new String[0]); + } + + private static boolean isPristine(File workingFile, File backupFile) { + if (!workingFile.isFile()) return true; + if (!backupFile.isFile()) return false; + return FileUtils.contentEquals(workingFile, backupFile); + } + + public void resyncAssetProfiles() { + profilesLoaded = false; + loadProfiles(false); + } + private void copyAssetProfilesIfNeeded() { InputControlsManager.getProfilesDir(context); @@ -114,35 +145,53 @@ private void copyAssetProfilesIfNeeded() { int newVersion = AppUtils.getVersionCode(context); int oldVersion = preferences.getInt("inputcontrols_app_version", 0); int oldSyncRevision = preferences.getInt("inputcontrols_asset_sync_revision", 0); - if (oldVersion == newVersion && oldSyncRevision >= ASSET_PROFILE_SYNC_REVISION) return; - preferences - .edit() - .putInt("inputcontrols_app_version", newVersion) - .putInt("inputcontrols_asset_sync_revision", ASSET_PROFILE_SYNC_REVISION) - .apply(); + String deviceToken = DeviceProfileSettings.assetProfilesToken(context); + String oldDeviceToken = preferences.getString("inputcontrols_asset_device_profile", null); + boolean deviceChanged = !deviceToken.equals(oldDeviceToken); + if (oldVersion == newVersion + && oldSyncRevision >= ASSET_PROFILE_SYNC_REVISION + && !deviceChanged) return; for (int id : RETIRED_PROFILE_IDS) { ControlsProfile.getProfileFile(context, id).delete(); getBackupFile(context, id).delete(); } - for (int id : REFRESHED_PROFILE_IDS) { - ControlsProfile.getProfileFile(context, id).delete(); + if (oldVersion != newVersion || oldSyncRevision < ASSET_PROFILE_SYNC_REVISION) { + for (int id : REFRESHED_PROFILE_IDS) { + ControlsProfile.getProfileFile(context, id).delete(); + } } - try { - AssetManager assetManager = context.getAssets(); - String[] assetFiles = assetManager.list("inputcontrols/profiles"); - if (assetFiles == null) return; - for (String assetFile : assetFiles) { - String assetPath = "inputcontrols/profiles/" + assetFile; - ControlsProfile originProfile = loadProfile(context, assetManager.open(assetPath)); - if (originProfile == null) continue; - File workingFile = ControlsProfile.getProfileFile(context, originProfile.id); - if (!workingFile.isFile()) FileUtils.copy(context, assetPath, workingFile); - FileUtils.copy(context, assetPath, getBackupFile(context, originProfile.id)); + AssetManager assetManager = context.getAssets(); + String assetDir = assetProfilesDir(deviceToken); + String[] assetFiles = listAssetProfiles(assetManager, assetDir); + if (assetFiles == null && !assetDir.equals(ASSET_PROFILES_DIR)) { + assetDir = ASSET_PROFILES_DIR; + assetFiles = listAssetProfiles(assetManager, assetDir); + } + if (assetFiles == null) return; + + for (String assetFile : assetFiles) { + String assetPath = assetDir + "/" + assetFile; + ControlsProfile originProfile; + try (InputStream inStream = assetManager.open(assetPath)) { + originProfile = loadProfile(context, inStream); + } catch (IOException e) { + continue; } - } catch (IOException e) { + if (originProfile == null) continue; + File workingFile = ControlsProfile.getProfileFile(context, originProfile.id); + File backupFile = getBackupFile(context, originProfile.id); + if (isPristine(workingFile, backupFile)) FileUtils.copy(context, assetPath, workingFile); + FileUtils.copy(context, assetPath, backupFile); } + + preferences + .edit() + .putInt("inputcontrols_app_version", newVersion) + .putInt("inputcontrols_asset_sync_revision", ASSET_PROFILE_SYNC_REVISION) + .putString("inputcontrols_asset_device_profile", deviceToken) + .apply(); } public void loadProfiles(boolean ignoreTemplates) { diff --git a/app/src/main/runtime/input/ui/InputControlsView.java b/app/src/main/runtime/input/ui/InputControlsView.java index f958e70bc..ebb609891 100644 --- a/app/src/main/runtime/input/ui/InputControlsView.java +++ b/app/src/main/runtime/input/ui/InputControlsView.java @@ -26,8 +26,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Set; import androidx.preference.PreferenceManager; import com.winlator.cmod.R; @@ -87,7 +89,7 @@ public class InputControlsView extends View { private Runnable hideControlsRunnable; // Runnable to hide the controls private SharedPreferences preferences; - private final SparseArray activeTouchElements = new SparseArray<>(); + private final SparseArray> activeTouchElements = new SparseArray<>(); private ControlElement stickElement; @@ -416,6 +418,7 @@ public synchronized void setProfile(ControlsProfile profile) { deselectAllElements(); } else this.profile = null; activeTouchElements.clear(); + syncCapturedPointers(); invalidate(); } @@ -504,20 +507,68 @@ public void setXServer(XServer xServer) { createMouseMoveTimer(); } + private void addCapture(int pointerId, ControlElement element) { + ArrayList captured = activeTouchElements.get(pointerId); + if (captured == null) { + captured = new ArrayList<>(2); + activeTouchElements.put(pointerId, captured); + } + if (!captured.contains(element)) captured.add(element); + } + + private void replaceCapture(int pointerId, ControlElement from, ControlElement to) { + ArrayList captured = activeTouchElements.get(pointerId); + if (captured == null) { + addCapture(pointerId, to); + return; + } + int index = captured.indexOf(from); + if (index == -1) { + if (!captured.contains(to)) captured.add(to); + return; + } + if (captured.contains(to)) captured.remove(index); + else captured.set(index, to); + } + + private List capturesFor(int pointerId) { + ArrayList captured = activeTouchElements.get(pointerId); + return captured == null ? java.util.Collections.emptyList() : captured; + } + + private boolean releaseCaptures(int pointerId, float x, float y, boolean withPosition) { + ArrayList captured = activeTouchElements.get(pointerId); + if (captured == null) return false; + boolean released = false; + for (int i = 0; i < captured.size(); i++) { + ControlElement element = captured.get(i); + if (element == null) continue; + boolean handled = + withPosition ? element.handleTouchUp(pointerId, x, y) : element.handleTouchUp(pointerId); + released |= handled; + } + activeTouchElements.remove(pointerId); + return released; + } + private void releaseActiveTouchElements() { - for (int i = 0; i < activeTouchElements.size(); i++) { - int activePointerId = activeTouchElements.keyAt(i); - ControlElement activeElement = activeTouchElements.valueAt(i); - if (activeElement != null) { - activeElement.handleTouchUp(activePointerId); - } + boolean batched = batchingUpdates; + batchingUpdates = true; + boolean released = false; + for (int i = activeTouchElements.size() - 1; i >= 0; i--) { + released |= releaseCaptures(activeTouchElements.keyAt(i), 0f, 0f, false); } activeTouchElements.clear(); + batchingUpdates = batched; + if (released) flushGamepadState(); + syncCapturedPointers(); } // Release captures whose pointer is no longer reported (missed UP/CANCEL). private void releaseStaleCaptures(MotionEvent event) { boolean removedAny = false; + boolean batched = batchingUpdates; + batchingUpdates = true; for (int i = activeTouchElements.size() - 1; i >= 0; i--) { int capturedId = activeTouchElements.keyAt(i); boolean stillDown = false; @@ -528,13 +579,21 @@ private void releaseStaleCaptures(MotionEvent event) { } } if (!stillDown) { - ControlElement element = activeTouchElements.valueAt(i); - if (element != null) element.handleTouchUp(capturedId); - activeTouchElements.removeAt(i); + releaseCaptures(capturedId, 0f, 0f, false); removedAny = true; } } - if (removedAny) syncCapturedPointers(); + batchingUpdates = batched; + if (removedAny) { + flushGamepadState(); + syncCapturedPointers(); + } + } + + private void flushGamepadState() { + if (batchingUpdates) return; + WinHandler winHandler = xServer != null ? xServer.getWinHandler() : null; + if (winHandler != null) winHandler.sendGamepadState(); } public void cancelContinuousMouseMove() { @@ -827,22 +886,30 @@ public boolean onTouchEvent(MotionEvent event) { float x = event.getX(actionIndex); float y = event.getY(actionIndex); + batchingUpdates = true; + if (stickElement != null && stickElement.handleTouchDown(pointerId, x, y)) { eventHandled = true; - activeTouchElements.put(pointerId, stickElement); + addCapture(pointerId, stickElement); } if (!eventHandled) { + boolean allowCoPress = true; for (ControlElement element : profile.getElements()) { - if (element.handleTouchDown(pointerId, x, y)) { + if (eventHandled + && (!allowCoPress || element.getType() != ControlElement.Type.BUTTON)) continue; + if (!element.handleTouchDown(pointerId, x, y)) continue; + addCapture(pointerId, element); + if (!eventHandled) { + allowCoPress = element.getType() != ControlElement.Type.RADIAL_MENU; eventHandled = true; - activeTouchElements.put(pointerId, element); - - if (hapticsEnabled) triggerTouchHaptic(); - break; } } + if (eventHandled && hapticsEnabled) triggerTouchHaptic(); } + + batchingUpdates = false; + if (eventHandled) flushGamepadState(); syncCapturedPointers(); if (!eventHandled) dispatchUnhandledTouch(event); break; @@ -859,54 +926,62 @@ public boolean onTouchEvent(MotionEvent event) { float x = event.getX(i); float y = event.getY(i); - ControlElement activeElement = activeTouchElements.get(movePointerId); boolean swipeAllowed = touchpadView == null || touchpadView.getScreenTouchMode() != TouchpadView.MODE_MAP_TO_RIGHT_STICK; boolean pointerHandled = false; - if (swipeAllowed - && activeElement != null - && activeElement.isCapturing(movePointerId) - && (activeElement.getType() == ControlElement.Type.RADIAL_MENU - || activeElement.getType() == ControlElement.Type.D_PAD) - && !activeElement.containsPoint(x, y)) { - for (ControlElement element : profile.getElements()) { - if (element.isSwipeTarget() && element.handleTouchDown(movePointerId, x, y)) { - activeElement.handleTouchUp(movePointerId, x, y); - activeTouchElements.put(movePointerId, element); - activeElement = element; - pointerHandled = true; - capturesChanged = true; - if (hapticsEnabled) triggerTouchHaptic(); - break; + List captured = capturesFor(movePointerId); + ControlElement[] snapshot = captured.toArray(new ControlElement[0]); + + for (ControlElement activeElement : snapshot) { + if (activeElement == null) continue; + + boolean handedOff = false; + if (swipeAllowed + && activeElement.isCapturing(movePointerId) + && (activeElement.getType() == ControlElement.Type.RADIAL_MENU + || activeElement.getType() == ControlElement.Type.D_PAD) + && !activeElement.containsPoint(x, y)) { + for (ControlElement element : profile.getElements()) { + if (element == activeElement) continue; + if (element.isSwipeTarget() && element.handleTouchDown(movePointerId, x, y)) { + activeElement.handleTouchUp(movePointerId, x, y); + replaceCapture(movePointerId, activeElement, element); + pointerHandled = true; + handedOff = true; + capturesChanged = true; + if (hapticsEnabled) triggerTouchHaptic(); + break; + } } } - } - if (!pointerHandled) { - pointerHandled = - activeElement != null && activeElement.handleTouchMove(movePointerId, x, y); - } + if (handedOff) continue; - if (swipeAllowed - && activeElement != null - && activeElement.getType() == ControlElement.Type.BUTTON - && !activeElement.isCapturing(movePointerId)) { - for (ControlElement element : profile.getElements()) { - if (element.isSwipeTarget() && element.handleTouchDown(movePointerId, x, y)) { - activeTouchElements.put(movePointerId, element); - pointerHandled = true; - capturesChanged = true; - if (hapticsEnabled) triggerTouchHaptic(); - break; + if (activeElement.handleTouchMove(movePointerId, x, y)) pointerHandled = true; + + if (swipeAllowed + && activeElement.getType() == ControlElement.Type.BUTTON + && activeElement.isSwipeTarget() + && !activeElement.containsPoint(x, y)) { + for (ControlElement element : profile.getElements()) { + if (element == activeElement) continue; + if (capturesFor(movePointerId).contains(element)) continue; + if (element.isSwipeTarget() && element.handleTouchDown(movePointerId, x, y)) { + addCapture(movePointerId, element); + pointerHandled = true; + capturesChanged = true; + if (hapticsEnabled) triggerTouchHaptic(); + break; + } } } } - if (!pointerHandled && activeElement == null) { + if (!pointerHandled && snapshot.length == 0) { if (stickElement != null && stickElement.handleTouchMove(movePointerId, x, y)) { - activeTouchElements.put(movePointerId, stickElement); + addCapture(movePointerId, stickElement); pointerHandled = true; capturesChanged = true; } @@ -914,7 +989,7 @@ public boolean onTouchEvent(MotionEvent event) { if (!pointerHandled) { for (ControlElement element : profile.getElements()) { if (element.handleTouchMove(movePointerId, x, y)) { - activeTouchElements.put(movePointerId, element); + addCapture(movePointerId, element); pointerHandled = true; capturesChanged = true; break; @@ -939,26 +1014,24 @@ public boolean onTouchEvent(MotionEvent event) { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: { - ControlElement activeElement = activeTouchElements.get(pointerId); float x = event.getX(actionIndex); float y = event.getY(actionIndex); - if (activeElement != null) { - eventHandled = activeElement.handleTouchUp(pointerId, x, y); - activeTouchElements.remove(pointerId); + batchingUpdates = true; + if (activeTouchElements.get(pointerId) != null) { + eventHandled = releaseCaptures(pointerId, x, y, true); } else { if (stickElement != null && stickElement.handleTouchUp(pointerId, x, y)) { eventHandled = true; } - if (!eventHandled) { - for (ControlElement element : profile.getElements()) { - if (element.handleTouchUp(pointerId, x, y)) { - eventHandled = true; - break; - } + for (ControlElement element : profile.getElements()) { + if (element.handleTouchUp(pointerId, x, y)) { + eventHandled = true; } } } + batchingUpdates = false; + if (eventHandled) flushGamepadState(); syncCapturedPointers(); if (!eventHandled) dispatchUnhandledTouch(event); break; diff --git a/app/src/test/kotlin/com/winlator/cmod/app/config/BundledControlLayoutTest.kt b/app/src/test/kotlin/com/winlator/cmod/app/config/BundledControlLayoutTest.kt new file mode 100644 index 000000000..361e9031f --- /dev/null +++ b/app/src/test/kotlin/com/winlator/cmod/app/config/BundledControlLayoutTest.kt @@ -0,0 +1,319 @@ +package com.winlator.cmod.app.config + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +class BundledControlLayoutTest { + private data class Box( + val left: Int, + val top: Int, + val right: Int, + val bottom: Int, + ) + + private val assetsRoot: File by lazy { + var dir: File? = File(".").canonicalFile + val suffixes = listOf("app/src/main/assets/inputcontrols", "src/main/assets/inputcontrols") + repeat(6) { + val here = dir ?: return@repeat + suffixes.forEach { suffix -> + val candidate = File(here, suffix) + if (candidate.isDirectory) return@lazy candidate + } + dir = here.parentFile + } + throw AssertionError("bundled input control assets not found from ${File(".").canonicalPath}") + } + + private fun layouts(dir: File): Map> { + val files = dir.listFiles { f -> f.name.endsWith(".icp") }.orEmpty() + return files.associate { f -> + val json = Json.parse(f.readText()).asMap() + json.num("id").toInt() to json + } + } + + private fun snapping(width: Int) = width / 100 + + private fun boxOf( + element: Map, + width: Int, + height: Int, + ): Box { + val snap = snapping(width) + val maxWidth = width / snap * snap + val maxHeight = height / snap * snap + val cx = Math.round(element.num("x") * maxWidth).toInt() + val cy = Math.round(element.num("y") * maxHeight).toInt() + val type = element.text("type") + val shape = element.text("shape") + val bindings = element.strings("bindings") + var halfWidth: Int + var halfHeight: Int + when (type) { + "BUTTON" -> + when (shape) { + "RECT", "ROUND_RECT" -> { + halfWidth = snap * 4 + halfHeight = snap * 2 + } + "SQUARE" -> { + halfWidth = (snap * 2.5f).toInt() + halfHeight = (snap * 2.5f).toInt() + } + else -> { + halfWidth = snap * 3 + halfHeight = snap * 3 + } + } + "D_PAD" -> { + halfWidth = snap * 7 + halfHeight = snap * 7 + } + "STICK", "TRACKPAD" -> { + halfWidth = snap * 6 + halfHeight = snap * 6 + } + "RANGE_BUTTON" -> { + halfWidth = snap * (bindings.size * 4 / 2) + halfHeight = snap * 2 + if ((element["orientation"] as? Number)?.toInt() == 1) { + val swap = halfWidth + halfWidth = halfHeight + halfHeight = swap + } + } + else -> { + halfWidth = snap * 3 + halfHeight = snap * 3 + } + } + val scale = ((element["scale"] as? Number)?.toDouble() ?: 1.0).toFloat() + halfWidth = (halfWidth * scale).toInt() + halfHeight = (halfHeight * scale).toInt() + return Box(cx - halfWidth, cy - halfHeight, cx + halfWidth, cy + halfHeight) + } + + private fun label(element: Map): String = + element.strings("bindings").firstOrNull { it != "NONE" } ?: element.text("type") + + @Test + fun everyDeviceVariantCoversTheSameProfileIds() { + val stock = layouts(File(assetsRoot, "profiles")) + assertTrue("stock layouts missing", stock.isNotEmpty()) + DeviceProfile.entries.filter { it.assetToken.isNotEmpty() }.forEach { profile -> + val dir = File(assetsRoot, "profiles-${profile.assetToken}") + assertTrue("missing asset dir for ${profile.name}: $dir", dir.isDirectory) + val variant = layouts(dir) + assertEquals( + "${profile.name} must ship the same profile ids as the stock layouts", + stock.keys.sorted(), + variant.keys.sorted(), + ) + variant.forEach { (id, json) -> + assertEquals( + "${profile.name} profile $id must keep the stock name so pickers stay stable", + stock.getValue(id).text("name"), + json.text("name"), + ) + } + } + } + + @Test + fun everyBundledElementStaysOnScreen() { + allLayoutDirs().forEach { dir -> + layouts(dir).forEach { (id, json) -> + val elements = json["elements"].asList().map { it.asMap() } + for (element in elements) { + val x = element.num("x") + val y = element.num("y") + assertTrue("${dir.name}/$id ${label(element)} x=$x out of range", x in 0.0..1.0) + assertTrue("${dir.name}/$id ${label(element)} y=$y out of range", y in 0.0..1.0) + } + } + } + } + + @Test + fun deviceVariantElementsDoNotOverlapOnTheTargetScreen() { + val width = 2400 + val height = 1504 + DeviceProfile.entries.filter { it.assetToken.isNotEmpty() }.forEach { profile -> + val dir = File(assetsRoot, "profiles-${profile.assetToken}") + layouts(dir).forEach { (id, json) -> + val elements = json["elements"].asList().map { it.asMap() } + val boxes = elements.map { boxOf(it, width, height) } + for (i in boxes.indices) { + val a = boxes[i] + assertTrue( + "${profile.name} profile $id ${label(elements[i])} runs off screen", + a.left >= 0 && a.top >= 0 && a.right <= width && a.bottom <= height, + ) + for (j in i + 1 until boxes.size) { + val b = boxes[j] + val overlaps = a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom + assertTrue( + "${profile.name} profile $id: ${label(elements[i])} overlaps " + label(elements[j]), + !overlaps, + ) + } + } + } + } + } + + @Test + fun everyTappableElementMeetsTheMinimumTouchTarget() { + val width = 2400 + val height = 1504 + val gameplayFloorPx = 108 + val menuFloorPx = 84 + DeviceProfile.entries.filter { it.assetToken.isNotEmpty() }.forEach { profile -> + val dir = File(assetsRoot, "profiles-${profile.assetToken}") + layouts(dir).forEach { (id, json) -> + val elements = json["elements"].asList().map { it.asMap() } + for (element in elements) { + val box = boxOf(element, width, height) + val shorter = minOf(box.right - box.left, box.bottom - box.top) + val floor = if (isDeliberateTarget(element, box, width)) menuFloorPx else gameplayFloorPx + assertTrue( + "${profile.name} profile $id ${label(element)} is only ${shorter}px across (floor $floor)", + shorter >= floor, + ) + } + } + } + } + + private fun isDeliberateTarget( + element: Map, + box: Box, + width: Int, + ): Boolean { + if (element.text("type") == "RANGE_BUTTON") return true + if (element.text("shape") != "ROUND_RECT") return false + val binding = element.strings("bindings").firstOrNull { it != "NONE" } + if (binding == "GAMEPAD_BUTTON_START" || binding == "GAMEPAD_BUTTON_SELECT") return true + if ((element["iconId"] as? Number)?.toInt() in listOf(15, 16)) return true + val centre = (box.left + box.right) / 2 + return Math.abs(centre - width / 2) <= 400 + } + + private fun allLayoutDirs(): List = + assetsRoot.listFiles { f -> f.isDirectory && f.name.startsWith("profiles") }.orEmpty().toList() +} + +private object Json { + fun parse(text: String): Any? = Reader(text).let { r -> r.value().also { r.skipWs() } } + + private class Reader(private val src: String) { + private var pos = 0 + + fun skipWs() { + while (pos < src.length && src[pos].isWhitespace()) pos++ + } + + fun value(): Any? { + skipWs() + return when (src[pos]) { + '{' -> obj() + '[' -> arr() + '"' -> str() + 't' -> { expect("true"); true } + 'f' -> { expect("false"); false } + 'n' -> { expect("null"); null } + else -> num() + } + } + + private fun expect(word: String) { + require(src.startsWith(word, pos)) { "expected $word at $pos" } + pos += word.length + } + + private fun obj(): Map { + val out = LinkedHashMap() + pos++ + skipWs() + if (src[pos] == '}') { pos++; return out } + while (true) { + skipWs() + val key = str() + skipWs() + require(src[pos] == ':') { "expected : at $pos" } + pos++ + out[key] = value() + skipWs() + when (src[pos]) { + ',' -> pos++ + '}' -> { pos++; return out } + else -> throw IllegalArgumentException("bad object at $pos") + } + } + } + + private fun arr(): List { + val out = ArrayList() + pos++ + skipWs() + if (src[pos] == ']') { pos++; return out } + while (true) { + out.add(value()) + skipWs() + when (src[pos]) { + ',' -> pos++ + ']' -> { pos++; return out } + else -> throw IllegalArgumentException("bad array at $pos") + } + } + } + + private fun str(): String { + require(src[pos] == '"') { "expected string at $pos" } + pos++ + val sb = StringBuilder() + while (src[pos] != '"') { + if (src[pos] == '\\') { + pos++ + when (val c = src[pos]) { + 'n' -> sb.append('\n') + 't' -> sb.append('\t') + 'r' -> sb.append('\r') + 'b' -> sb.append('\b') + 'u' -> { + sb.append(src.substring(pos + 1, pos + 5).toInt(16).toChar()) + pos += 4 + } + else -> sb.append(c) + } + } else { + sb.append(src[pos]) + } + pos++ + } + pos++ + return sb.toString() + } + + private fun num(): Double { + val start = pos + while (pos < src.length && (src[pos].isDigit() || src[pos] in "-+.eE")) pos++ + return src.substring(start, pos).toDouble() + } + } +} + +@Suppress("UNCHECKED_CAST") +private fun Any?.asMap(): Map = this as Map + +@Suppress("UNCHECKED_CAST") +private fun Any?.asList(): List = this as List + +private fun Map.num(key: String): Double = (this[key] as Number).toDouble() + +private fun Map.text(key: String): String = this[key] as String + +private fun Map.strings(key: String): List = this[key].asList().map { it as String } diff --git a/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt new file mode 100644 index 000000000..6e1ac2929 --- /dev/null +++ b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt @@ -0,0 +1,83 @@ +package com.winlator.cmod.app.config + +import org.junit.Assert.assertEquals +import org.junit.Test + +class DeviceProfileTest { + private val astra2Build = "nubia redmagic NP06J PQ85P01_A PQ85P01-UN qcom" + private val astra2Marketing = "REDMAGIC Astra 2 Gaming Tablet" + + @Test + fun marketingNameIdentifiesAstra2() { + assertEquals(DeviceProfile.ASTRA_2, DeviceProfile.classify("", astra2Marketing)) + } + + @Test + fun buildTokensIdentifyAstra2WithoutMarketingName() { + assertEquals(DeviceProfile.ASTRA_2, DeviceProfile.classify(astra2Build, "")) + } + + @Test + fun regionalModelNamesStillMatch() { + val regional = + listOf( + "nubia redmagic NP06J PQ85P01_A PQ85P01-UN qcom", + "nubia redmagic NP06J PQ85P01_B PQ85P01-CN qcom", + "nubia redmagic NP06P PQ85P01_A PQ85P01-EEA qcom", + ) + regional.forEach { assertEquals(DeviceProfile.ASTRA_2, DeviceProfile.classify(it, "")) } + } + + @Test + fun otherRedMagicDevicesAreNotAstra2() { + val others = + listOf( + "nubia redmagic NX769J aurora aurora-global qcom" to "REDMAGIC 9 Pro", + "nubia redmagic NX729J peak peak-global qcom" to "REDMAGIC 8 Pro", + "nubia nubia NX721J muse muse-global qcom" to "REDMAGIC Astra Gaming Tablet", + ) + others.forEach { (build, marketing) -> + assertEquals(DeviceProfile.DEFAULT, DeviceProfile.classify(build, marketing)) + } + } + + @Test + fun unrelatedDevicesFallBackToDefault() { + val others = + listOf( + "oneplus oneplus CPH2649 OP5959L1 CPH2649 qcom" to "OnePlus 13", + "google google Pixel 9 Pro caiman caiman zuma" to "Pixel 9 Pro", + "samsung samsung SM-X910 gts9ultra gts9ultrxx qcom" to "Galaxy Tab S9 Ultra", + "" to "", + ) + others.forEach { (build, marketing) -> + assertEquals(DeviceProfile.DEFAULT, DeviceProfile.classify(build, marketing)) + } + } + + @Test + fun matchingIsCaseInsensitive() { + assertEquals(DeviceProfile.ASTRA_2, DeviceProfile.classify("NUBIA REDMAGIC PQ85P01_A", "")) + assertEquals(DeviceProfile.ASTRA_2, DeviceProfile.classify("", "redmagic ASTRA 2 gaming tablet")) + } + + @Test + fun prefValuesRoundTrip() { + DeviceProfile.entries.forEach { + assertEquals(it, DeviceProfile.fromPrefValue(it.prefValue)) + } + } + + @Test + fun unknownPrefValueFallsBackToDefault() { + assertEquals(DeviceProfile.DEFAULT, DeviceProfile.fromPrefValue(null)) + assertEquals(DeviceProfile.DEFAULT, DeviceProfile.fromPrefValue("")) + assertEquals(DeviceProfile.DEFAULT, DeviceProfile.fromPrefValue("some_retired_profile")) + } + + @Test + fun defaultProfileUsesTheLegacyAssetDirectory() { + assertEquals("", DeviceProfile.DEFAULT.assetToken) + assertEquals("astra2", DeviceProfile.ASTRA_2.assetToken) + } +} From 71cf2d33a0b3d8425654fa2234378df778645759 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 13:13:58 -0400 Subject: [PATCH 02/10] Make the Astra 2 sticks uniform and pull every thumb cluster down The first pass placed the two sticks asymmetrically - left at 25.7 mm and high, right at 23.4 mm and low - because it was reproducing an Xbox face, where the sticks sit at different heights. On a touchscreen there is no handle offset to justify that, and it left the clusters spread over the full height of the panel. Both sticks are now identical and exactly mirrored: 23.4 mm, centred at x = 460 and x = 1940, both at y = 1150, both 43.1 mm from their grip pivot. The D-pad and the ABXY diamond mirror each other too, at x = 301 and x = 2099, sharing y = 710. Everything moved down and closer together. Measured from the pivot 5 mm outboard of the panel and 37 mm up from the bottom: control before now face buttons 55.0 mm 50.5 mm D-pad 55.1 mm 40.6 mm right stick 49.7 mm 43.1 mm L3 / R3 29 / 61 mm 27.8 mm both Every thumb control now lives in the bottom 78 mm of the 123 mm panel instead of being spread over 100 mm, and the ABXY diamond is tighter - a 155 px radius with 11.7 mm buttons, so the thumb covers all four without leaving the cluster. L3 and R3 moved into the bottom outer corners, the easiest reach on the panel. The diamond radius also has to clear twice the button half-size or the square bounding boxes overlap even when the drawn circles do not, and the engine hit tests against the box. GameHub now shares the Virtual Gamepad geometry outright rather than carrying its own scales, since VisualStyle only changes how elements are drawn. RTS keys drop to scale 1.05 on a 190 px row pitch so the grid keeps a 5 mm gap and still clears the bottom edge. --- .../profiles-astra2/controls-1.icp | 40 +++++------ .../profiles-astra2/controls-2.icp | 38 +++++----- .../profiles-astra2/controls-3.icp | 48 ++++++------- .../profiles-astra2/controls-6.icp | 70 +++++++++---------- .../profiles-astra2/controls-7.icp | 64 ++++++++--------- .../profiles-astra2/controls-8.icp | 44 ++++++------ 6 files changed, 152 insertions(+), 152 deletions(-) diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp index e15237bc8..11b5fe5bb 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp @@ -46,7 +46,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.104167, "y": 0.376344, "toggleSwitch": false, @@ -62,7 +62,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.1875, "y": 0.376344, "toggleSwitch": false, @@ -78,7 +78,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.104167, "y": 0.504032, "toggleSwitch": false, @@ -94,7 +94,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.1875, "y": 0.504032, "toggleSwitch": false, @@ -110,7 +110,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.104167, "y": 0.63172, "toggleSwitch": false, @@ -126,7 +126,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.1875, "y": 0.63172, "toggleSwitch": false, @@ -142,7 +142,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.104167, "y": 0.759409, "toggleSwitch": false, @@ -158,7 +158,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.1875, "y": 0.759409, "toggleSwitch": false, @@ -174,7 +174,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.104167, "y": 0.887097, "toggleSwitch": false, @@ -190,7 +190,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.1875, "y": 0.887097, "toggleSwitch": false, @@ -206,8 +206,8 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.820833, + "scale": 1.05, + "x": 0.816667, "y": 0.658602, "toggleSwitch": false, "text": "", @@ -222,9 +222,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.820833, - "y": 0.799731, + "scale": 1.05, + "x": 0.816667, + "y": 0.78629, "toggleSwitch": false, "text": "", "iconId": 0 @@ -238,8 +238,8 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.908333, + "scale": 1.05, + "x": 0.9, "y": 0.658602, "toggleSwitch": false, "text": "", @@ -254,9 +254,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.908333, - "y": 0.799731, + "scale": 1.05, + "x": 0.9, + "y": 0.78629, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp index 14cd8ab14..519554b03 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -76,9 +76,9 @@ "KEY_S", "KEY_A" ], - "scale": 1.05, - "x": 0.145833, - "y": 0.672043, + "scale": 1.0, + "x": 0.191667, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -93,8 +93,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.420027, + "x": 0.874583, + "y": 0.372984, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "NONE" ], "scale": 1.0, - "x": 0.797917, - "y": 0.530914, + "x": 0.81, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.0, - "x": 0.935417, - "y": 0.530914, + "x": 0.939167, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.641801, + "x": 0.874583, + "y": 0.581317, "toggleSwitch": false, "text": "", "iconId": 0 @@ -157,8 +157,8 @@ "NONE" ], "scale": 1.0, - "x": 0.902083, - "y": 0.840054, + "x": 0.066667, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -173,8 +173,8 @@ "NONE" ], "scale": 1.0, - "x": 0.275, - "y": 0.840054, + "x": 0.933333, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -189,8 +189,8 @@ "NONE" ], "scale": 0.95, - "x": 0.427083, - "y": 0.893817, + "x": 0.4375, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 0 @@ -205,8 +205,8 @@ "NONE" ], "scale": 0.95, - "x": 0.572917, - "y": 0.893817, + "x": 0.5625, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index 030888f0d..e70711af1 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -76,9 +76,9 @@ "GAMEPAD_LEFT_THUMB_DOWN", "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 1.1, - "x": 0.145833, - "y": 0.672043, + "scale": 1.0, + "x": 0.191667, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -92,9 +92,9 @@ "GAMEPAD_DPAD_DOWN", "GAMEPAD_DPAD_LEFT" ], - "scale": 0.95, - "x": 0.191667, - "y": 0.416667, + "scale": 0.85, + "x": 0.125417, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.783333, - "y": 0.833333, + "x": 0.808333, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.420027, + "x": 0.874583, + "y": 0.372984, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.0, - "x": 0.797917, - "y": 0.530914, + "x": 0.81, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -157,8 +157,8 @@ "NONE" ], "scale": 1.0, - "x": 0.935417, - "y": 0.530914, + "x": 0.939167, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -173,8 +173,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.641801, + "x": 0.874583, + "y": 0.581317, "toggleSwitch": false, "text": "", "iconId": 0 @@ -189,8 +189,8 @@ "NONE" ], "scale": 1.0, - "x": 0.902083, - "y": 0.840054, + "x": 0.933333, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -205,8 +205,8 @@ "NONE" ], "scale": 1.0, - "x": 0.275, - "y": 0.840054, + "x": 0.066667, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -221,8 +221,8 @@ "NONE" ], "scale": 0.95, - "x": 0.427083, - "y": 0.893817, + "x": 0.4375, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 16 @@ -237,8 +237,8 @@ "NONE" ], "scale": 0.95, - "x": 0.572917, - "y": 0.893817, + "x": 0.5625, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 15 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index 48e5f8350..ce70dcab6 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -47,8 +47,8 @@ "KEY_M" ], "scale": 1.1, - "x": 0.083333, - "y": 0.349462, + "x": 0.258333, + "y": 0.376344, "toggleSwitch": false, "text": "", "iconId": 0 @@ -63,8 +63,8 @@ "KEY_T" ], "scale": 1.1, - "x": 0.933333, - "y": 0.336022, + "x": 0.741667, + "y": 0.376344, "toggleSwitch": false, "text": "", "iconId": 0 @@ -78,9 +78,9 @@ "KEY_S", "KEY_A" ], - "scale": 1.1, - "x": 0.145833, - "y": 0.672043, + "scale": 1.0, + "x": 0.191667, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -94,9 +94,9 @@ "KEY_DOWN", "KEY_LEFT" ], - "scale": 0.75, - "x": 0.191667, - "y": 0.416667, + "scale": 0.85, + "x": 0.125417, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -111,8 +111,8 @@ "NONE" ], "scale": 1.0, - "x": 0.125, - "y": 0.880376, + "x": 0.0625, + "y": 0.692204, "toggleSwitch": true, "text": "", "iconId": 0 @@ -127,7 +127,7 @@ "NONE" ], "scale": 1.0, - "x": 0.220833, + "x": 0.066667, "y": 0.880376, "toggleSwitch": true, "text": "", @@ -143,8 +143,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.493952, + "x": 0.874583, + "y": 0.372984, "toggleSwitch": false, "text": "", "iconId": 0 @@ -159,8 +159,8 @@ "NONE" ], "scale": 1.0, - "x": 0.797917, - "y": 0.604839, + "x": 0.81, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -175,8 +175,8 @@ "NONE" ], "scale": 1.0, - "x": 0.935417, - "y": 0.604839, + "x": 0.939167, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -191,8 +191,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.715726, + "x": 0.874583, + "y": 0.581317, "toggleSwitch": false, "text": "", "iconId": 0 @@ -207,8 +207,8 @@ "NONE" ], "scale": 1.15, - "x": 0.783333, - "y": 0.833333, + "x": 0.808333, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -217,15 +217,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_ALT_L", + "KEY_TAB", "NONE", "NONE", "NONE" ], "scale": 1.0, - "x": 0.902083, - "y": 0.840054, - "toggleSwitch": true, + "x": 0.9375, + "y": 0.692204, + "toggleSwitch": false, "text": "", "iconId": 0 }, @@ -233,15 +233,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_TAB", + "KEY_ALT_L", "NONE", "NONE", "NONE" ], "scale": 1.0, - "x": 0.708333, - "y": 0.672043, - "toggleSwitch": false, + "x": 0.933333, + "y": 0.880376, + "toggleSwitch": true, "text": "", "iconId": 0 }, @@ -255,8 +255,8 @@ "NONE" ], "scale": 0.95, - "x": 0.427083, - "y": 0.893817, + "x": 0.4375, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 0 @@ -271,8 +271,8 @@ "NONE" ], "scale": 0.95, - "x": 0.572917, - "y": 0.893817, + "x": 0.5625, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp index f7cf2d69d..94968a54e 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -12,7 +12,7 @@ "NONE", "NONE" ], - "scale": 1.25, + "scale": 1.2, "x": 0.1, "y": 0.087366, "toggleSwitch": false, @@ -28,7 +28,7 @@ "NONE", "NONE" ], - "scale": 1.25, + "scale": 1.2, "x": 0.9, "y": 0.087366, "toggleSwitch": false, @@ -44,7 +44,7 @@ "NONE", "NONE" ], - "scale": 1.25, + "scale": 1.2, "x": 0.1, "y": 0.208333, "toggleSwitch": false, @@ -60,7 +60,7 @@ "NONE", "NONE" ], - "scale": 1.25, + "scale": 1.2, "x": 0.9, "y": 0.208333, "toggleSwitch": false, @@ -76,9 +76,9 @@ "GAMEPAD_LEFT_THUMB_DOWN", "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 1.1, - "x": 0.145833, - "y": 0.672043, + "scale": 1.0, + "x": 0.191667, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -92,9 +92,9 @@ "GAMEPAD_DPAD_DOWN", "GAMEPAD_DPAD_LEFT" ], - "scale": 1.0, - "x": 0.191667, - "y": 0.416667, + "scale": 0.85, + "x": 0.125417, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.783333, - "y": 0.833333, + "x": 0.808333, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.05, - "x": 0.866667, - "y": 0.420027, + "scale": 1.0, + "x": 0.874583, + "y": 0.372984, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.05, - "x": 0.797917, - "y": 0.530914, + "scale": 1.0, + "x": 0.81, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.05, - "x": 0.935417, - "y": 0.530914, + "scale": 1.0, + "x": 0.939167, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.05, - "x": 0.866667, - "y": 0.641801, + "scale": 1.0, + "x": 0.874583, + "y": 0.581317, "toggleSwitch": false, "text": "", "iconId": 0 @@ -189,8 +189,8 @@ "NONE" ], "scale": 1.0, - "x": 0.902083, - "y": 0.840054, + "x": 0.933333, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -205,8 +205,8 @@ "NONE" ], "scale": 1.0, - "x": 0.275, - "y": 0.840054, + "x": 0.066667, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -221,8 +221,8 @@ "NONE" ], "scale": 0.95, - "x": 0.427083, - "y": 0.893817, + "x": 0.4375, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 16 @@ -237,8 +237,8 @@ "NONE" ], "scale": 0.95, - "x": 0.572917, - "y": 0.893817, + "x": 0.5625, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 15 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp index faac9978a..e305411c9 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -76,9 +76,9 @@ "GAMEPAD_LEFT_THUMB_DOWN", "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 1.1, - "x": 0.145833, - "y": 0.672043, + "scale": 1.0, + "x": 0.191667, + "y": 0.772849, "toggleSwitch": false, "text": "", "iconId": 0 @@ -92,9 +92,9 @@ "GAMEPAD_DPAD_DOWN", "GAMEPAD_DPAD_LEFT" ], - "scale": 1.0, - "x": 0.2, - "y": 0.416667, + "scale": 0.95, + "x": 0.125417, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.420027, + "x": 0.874583, + "y": 0.372984, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.0, - "x": 0.797917, - "y": 0.530914, + "x": 0.81, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.0, - "x": 0.935417, - "y": 0.530914, + "x": 0.939167, + "y": 0.477151, "toggleSwitch": false, "text": "", "iconId": 0 @@ -157,8 +157,8 @@ "NONE" ], "scale": 1.0, - "x": 0.866667, - "y": 0.641801, + "x": 0.874583, + "y": 0.581317, "toggleSwitch": false, "text": "", "iconId": 0 @@ -173,8 +173,8 @@ "NONE" ], "scale": 1.0, - "x": 0.902083, - "y": 0.840054, + "x": 0.933333, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -189,8 +189,8 @@ "NONE" ], "scale": 1.0, - "x": 0.275, - "y": 0.840054, + "x": 0.066667, + "y": 0.880376, "toggleSwitch": false, "text": "", "iconId": 0 @@ -205,8 +205,8 @@ "NONE" ], "scale": 0.95, - "x": 0.427083, - "y": 0.893817, + "x": 0.4375, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 16 @@ -221,8 +221,8 @@ "NONE" ], "scale": 0.95, - "x": 0.572917, - "y": 0.893817, + "x": 0.5625, + "y": 0.900538, "toggleSwitch": false, "text": "", "iconId": 15 From 45b08fd3a2546dc000f5c44d229219f27528699f Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 14:00:23 -0400 Subject: [PATCH 03/10] Re-derive the Astra 2 layout from measured hardware and a measured thumb rest The previous two passes were tuned against a guessed grip. Reviewing them turned up three things that invalidated parts of that work. First, the hardware. The Astra 2 body is 207.1 x 134.2 x 6.9 mm at 363 g, the panel is 195.0 x 123.0 mm (310.6 x 312.6 dpi, 9.08 in), there is no display cutout anywhere, and the tablet has NO shoulder triggers of any kind - physical or capacitive - unlike RedMagic's phones. So every bumper and trigger has to be on-screen, and the index fingers are not pinned to the top edge by hardware. The top edge does carry the volume rocker and the red Magic Key, which jumps straight into Game Space; an index finger curling over the top-right corner can hit it, so the triggers moved inboard from the extreme corners (RT now has 21.5 mm of clearance from the right edge instead of 7 mm). Second, the grip. Reach was being computed from a pivot assumed to sit 5 mm outboard of the panel edge, 37 mm up. Published anthropometry puts the thumb MCP on the glass, roughly 20-35 mm inboard, at or just below panel centre - a difference larger than the entire design tolerance. Evaluating the shipped layout both ways gave contradictory verdicts, so the pivot was measured instead: 25 seconds of raw contacts off /dev/input/event6 while the tablet was held normally, 1030 samples splitting 523/507 into two clusters, both medians at 33 mm inboard and 35 mm up from the bottom edge. That is now the anchor, and it is a measurement rather than a model. Third, spacing. Adjacent face buttons were 17.8 mm centre to centre. A thumb contact patch is about 25 mm wide, so independent presses need roughly 20 mm of centre-to-centre separation; below that the thumb straddles two targets. The diamond radius went from 155 to 174 px, which puts adjacent centres at exactly 20.0 mm, and the face buttons grew from 11.7 to 13.3 mm - above the measured 9.2-9.6 mm thumb plateau with margin for the +25% scatter seen away from screen centre. Every element is now inside its limit under all three candidate pivot models, not just one: held controls (both sticks, D-pad) never exceed 45 mm and taps never exceed 60 mm whether the grip is low, middle or high. Measured against the captured thumb rest, nothing sits more than 40.5 mm away and most controls are within 30 mm. The sticks stay uniform and exactly mirrored at 23.4 mm. Worth noting that the hand-edited Virtual Gamepad already on the tablet places its sticks 30-37 mm inboard and 24 mm up, which is within a few millimetres of both the measured thumb rest and where this layout puts them - three independent estimates agreeing. That profile is left untouched by the sync because it differs from its pristine backup. --- .../profiles-astra2/controls-1.icp | 60 ++++---- .../profiles-astra2/controls-2.icp | 58 ++++---- .../profiles-astra2/controls-3.icp | 92 ++++++------ .../profiles-astra2/controls-6.icp | 132 ++++++++---------- .../profiles-astra2/controls-7.icp | 92 ++++++------ .../profiles-astra2/controls-8.icp | 88 ++++++------ 6 files changed, 253 insertions(+), 269 deletions(-) diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp index 11b5fe5bb..c086a56f7 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.0, - "x": 0.141667, - "y": 0.094086, + "x": 0.175, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0, @@ -30,8 +30,8 @@ "NONE" ], "scale": 1.0, - "x": 0.858333, - "y": 0.094086, + "x": 0.825, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0, @@ -47,8 +47,8 @@ "NONE" ], "scale": 1.05, - "x": 0.104167, - "y": 0.376344, + "x": 0.125, + "y": 0.389785, "toggleSwitch": false, "text": "", "iconId": 0 @@ -63,8 +63,8 @@ "NONE" ], "scale": 1.05, - "x": 0.1875, - "y": 0.376344, + "x": 0.208333, + "y": 0.389785, "toggleSwitch": false, "text": "", "iconId": 0 @@ -79,8 +79,8 @@ "NONE" ], "scale": 1.05, - "x": 0.104167, - "y": 0.504032, + "x": 0.125, + "y": 0.514113, "toggleSwitch": false, "text": "", "iconId": 0 @@ -95,8 +95,8 @@ "NONE" ], "scale": 1.05, - "x": 0.1875, - "y": 0.504032, + "x": 0.208333, + "y": 0.514113, "toggleSwitch": false, "text": "", "iconId": 3 @@ -111,8 +111,8 @@ "NONE" ], "scale": 1.05, - "x": 0.104167, - "y": 0.63172, + "x": 0.125, + "y": 0.638441, "toggleSwitch": false, "text": "", "iconId": 2 @@ -127,8 +127,8 @@ "NONE" ], "scale": 1.05, - "x": 0.1875, - "y": 0.63172, + "x": 0.208333, + "y": 0.638441, "toggleSwitch": false, "text": "", "iconId": 4 @@ -143,8 +143,8 @@ "NONE" ], "scale": 1.05, - "x": 0.104167, - "y": 0.759409, + "x": 0.125, + "y": 0.762769, "toggleSwitch": false, "text": "", "iconId": 0 @@ -159,8 +159,8 @@ "NONE" ], "scale": 1.05, - "x": 0.1875, - "y": 0.759409, + "x": 0.208333, + "y": 0.762769, "toggleSwitch": false, "text": "", "iconId": 5 @@ -175,7 +175,7 @@ "NONE" ], "scale": 1.05, - "x": 0.104167, + "x": 0.125, "y": 0.887097, "toggleSwitch": false, "text": "", @@ -191,7 +191,7 @@ "NONE" ], "scale": 1.05, - "x": 0.1875, + "x": 0.208333, "y": 0.887097, "toggleSwitch": false, "text": "", @@ -207,8 +207,8 @@ "NONE" ], "scale": 1.05, - "x": 0.816667, - "y": 0.658602, + "x": 0.825, + "y": 0.638441, "toggleSwitch": false, "text": "", "iconId": 0 @@ -223,8 +223,8 @@ "NONE" ], "scale": 1.05, - "x": 0.816667, - "y": 0.78629, + "x": 0.825, + "y": 0.766129, "toggleSwitch": false, "text": "", "iconId": 0 @@ -239,8 +239,8 @@ "NONE" ], "scale": 1.05, - "x": 0.9, - "y": 0.658602, + "x": 0.908333, + "y": 0.638441, "toggleSwitch": false, "text": "", "iconId": 0 @@ -255,8 +255,8 @@ "NONE" ], "scale": 1.05, - "x": 0.9, - "y": 0.78629, + "x": 0.908333, + "y": 0.766129, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp index 519554b03..eb2811103 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.087366, + "x": 0.158333, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -29,8 +29,8 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.087366, + "x": 0.841667, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -45,8 +45,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.208333, + "x": 0.158333, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 @@ -61,8 +61,8 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.208333, + "x": 0.841667, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 @@ -76,9 +76,9 @@ "KEY_S", "KEY_A" ], - "scale": 1.0, - "x": 0.191667, - "y": 0.772849, + "scale": 0.85, + "x": 0.1375, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -92,9 +92,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.372984, + "scale": 1.15, + "x": 0.8625, + "y": 0.393817, "toggleSwitch": false, "text": "", "iconId": 0 @@ -108,9 +108,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.81, - "y": 0.477151, + "scale": 1.15, + "x": 0.79, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.939167, - "y": 0.477151, + "scale": 1.15, + "x": 0.935, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.581317, + "scale": 1.15, + "x": 0.8625, + "y": 0.627688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.066667, - "y": 0.880376, + "scale": 1.15, + "x": 0.258333, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.933333, - "y": 0.880376, + "scale": 1.15, + "x": 0.741667, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index e70711af1..94ce4b748 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.087366, + "x": 0.158333, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -29,8 +29,8 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.087366, + "x": 0.841667, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -45,8 +45,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.208333, + "x": 0.158333, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 @@ -61,40 +61,40 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.208333, + "x": 0.841667, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "STICK", + "type": "D_PAD", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_LEFT_THUMB_UP", - "GAMEPAD_LEFT_THUMB_RIGHT", - "GAMEPAD_LEFT_THUMB_DOWN", - "GAMEPAD_LEFT_THUMB_LEFT" + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" ], - "scale": 1.0, - "x": 0.191667, - "y": 0.772849, + "scale": 0.85, + "x": 0.1375, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "D_PAD", + "type": "STICK", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_DPAD_UP", - "GAMEPAD_DPAD_RIGHT", - "GAMEPAD_DPAD_DOWN", - "GAMEPAD_DPAD_LEFT" + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 0.85, - "x": 0.125417, - "y": 0.477151, + "scale": 1.0, + "x": 0.1375, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.808333, - "y": 0.772849, + "x": 0.8625, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.372984, + "scale": 1.15, + "x": 0.8625, + "y": 0.393817, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.81, - "y": 0.477151, + "scale": 1.15, + "x": 0.79, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.939167, - "y": 0.477151, + "scale": 1.15, + "x": 0.935, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.581317, + "scale": 1.15, + "x": 0.8625, + "y": 0.627688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -183,14 +183,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_L3", + "GAMEPAD_BUTTON_R3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.933333, - "y": 0.880376, + "scale": 1.15, + "x": 0.258333, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 @@ -199,14 +199,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_R3", + "GAMEPAD_BUTTON_L3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.066667, - "y": 0.880376, + "scale": 1.15, + "x": 0.741667, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index ce70dcab6..3531c6c03 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.0, - "x": 0.141667, - "y": 0.094086, + "x": 0.175, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0, @@ -30,8 +30,8 @@ "NONE" ], "scale": 1.0, - "x": 0.858333, - "y": 0.094086, + "x": 0.825, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0, @@ -47,8 +47,8 @@ "KEY_M" ], "scale": 1.1, - "x": 0.258333, - "y": 0.376344, + "x": 0.158333, + "y": 0.241935, "toggleSwitch": false, "text": "", "iconId": 0 @@ -63,41 +63,25 @@ "KEY_T" ], "scale": 1.1, - "x": 0.741667, - "y": 0.376344, + "x": 0.841667, + "y": 0.241935, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "STICK", - "shape": "CIRCLE", - "bindings": [ - "KEY_W", - "KEY_D", - "KEY_S", - "KEY_A" - ], - "scale": 1.0, - "x": 0.191667, - "y": 0.772849, - "toggleSwitch": false, - "text": "", - "iconId": 0 - }, - { - "type": "D_PAD", + "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_UP", - "KEY_RIGHT", - "KEY_DOWN", - "KEY_LEFT" + "KEY_SHIFT_L", + "NONE", + "NONE", + "NONE" ], - "scale": 0.85, - "x": 0.125417, - "y": 0.477151, - "toggleSwitch": false, + "scale": 1.15, + "x": 0.1375, + "y": 0.510753, + "toggleSwitch": true, "text": "", "iconId": 0 }, @@ -105,15 +89,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_CTRL_L", + "KEY_R", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.0625, - "y": 0.692204, - "toggleSwitch": true, + "scale": 1.15, + "x": 0.8625, + "y": 0.393817, + "toggleSwitch": false, "text": "", "iconId": 0 }, @@ -121,15 +105,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_SHIFT_L", + "KEY_E", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.066667, - "y": 0.880376, - "toggleSwitch": true, + "scale": 1.15, + "x": 0.79, + "y": 0.510753, + "toggleSwitch": false, "text": "", "iconId": 0 }, @@ -137,14 +121,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_R", + "MOUSE_LEFT_BUTTON", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.372984, + "scale": 1.15, + "x": 0.935, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -153,30 +137,30 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_E", + "KEY_SPACE", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.81, - "y": 0.477151, + "scale": 1.15, + "x": 0.8625, + "y": 0.627688, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "BUTTON", + "type": "STICK", "shape": "CIRCLE", "bindings": [ - "MOUSE_LEFT_BUTTON", - "NONE", - "NONE", - "NONE" + "KEY_W", + "KEY_D", + "KEY_S", + "KEY_A" ], "scale": 1.0, - "x": 0.939167, - "y": 0.477151, + "x": 0.1375, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -185,14 +169,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_SPACE", + "MOUSE_RIGHT_BUTTON", "NONE", "NONE", "NONE" ], "scale": 1.0, - "x": 0.874583, - "y": 0.581317, + "x": 0.8625, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -201,15 +185,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "MOUSE_RIGHT_BUTTON", + "KEY_CTRL_L", "NONE", "NONE", "NONE" ], "scale": 1.15, - "x": 0.808333, - "y": 0.772849, - "toggleSwitch": false, + "x": 0.258333, + "y": 0.705645, + "toggleSwitch": true, "text": "", "iconId": 0 }, @@ -217,15 +201,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_TAB", + "KEY_ALT_L", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.9375, - "y": 0.692204, - "toggleSwitch": false, + "scale": 1.15, + "x": 0.741667, + "y": 0.705645, + "toggleSwitch": true, "text": "", "iconId": 0 }, @@ -233,15 +217,15 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "KEY_ALT_L", + "KEY_TAB", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.933333, + "scale": 1.15, + "x": 0.6875, "y": 0.880376, - "toggleSwitch": true, + "toggleSwitch": false, "text": "", "iconId": 0 }, diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp index 94968a54e..da9a1f5a5 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.087366, + "x": 0.158333, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -29,8 +29,8 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.087366, + "x": 0.841667, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -45,8 +45,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.208333, + "x": 0.158333, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 @@ -61,40 +61,40 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.208333, + "x": 0.841667, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "STICK", + "type": "D_PAD", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_LEFT_THUMB_UP", - "GAMEPAD_LEFT_THUMB_RIGHT", - "GAMEPAD_LEFT_THUMB_DOWN", - "GAMEPAD_LEFT_THUMB_LEFT" + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" ], - "scale": 1.0, - "x": 0.191667, - "y": 0.772849, + "scale": 0.85, + "x": 0.1375, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "D_PAD", + "type": "STICK", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_DPAD_UP", - "GAMEPAD_DPAD_RIGHT", - "GAMEPAD_DPAD_DOWN", - "GAMEPAD_DPAD_LEFT" + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 0.85, - "x": 0.125417, - "y": 0.477151, + "scale": 1.0, + "x": 0.1375, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.808333, - "y": 0.772849, + "x": 0.8625, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.372984, + "scale": 1.15, + "x": 0.8625, + "y": 0.393817, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.81, - "y": 0.477151, + "scale": 1.15, + "x": 0.79, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.939167, - "y": 0.477151, + "scale": 1.15, + "x": 0.935, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.581317, + "scale": 1.15, + "x": 0.8625, + "y": 0.627688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -183,14 +183,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_L3", + "GAMEPAD_BUTTON_R3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.933333, - "y": 0.880376, + "scale": 1.15, + "x": 0.258333, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 @@ -199,14 +199,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_R3", + "GAMEPAD_BUTTON_L3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.066667, - "y": 0.880376, + "scale": 1.15, + "x": 0.741667, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp index e305411c9..099921d90 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -13,8 +13,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.087366, + "x": 0.158333, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -29,8 +29,8 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.087366, + "x": 0.841667, + "y": 0.091398, "toggleSwitch": false, "text": "", "iconId": 0 @@ -45,8 +45,8 @@ "NONE" ], "scale": 1.2, - "x": 0.1, - "y": 0.208333, + "x": 0.158333, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 @@ -61,40 +61,40 @@ "NONE" ], "scale": 1.2, - "x": 0.9, - "y": 0.208333, + "x": 0.841667, + "y": 0.212366, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "STICK", + "type": "D_PAD", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_LEFT_THUMB_UP", - "GAMEPAD_LEFT_THUMB_RIGHT", - "GAMEPAD_LEFT_THUMB_DOWN", - "GAMEPAD_LEFT_THUMB_LEFT" + "GAMEPAD_DPAD_UP", + "GAMEPAD_DPAD_RIGHT", + "GAMEPAD_DPAD_DOWN", + "GAMEPAD_DPAD_LEFT" ], - "scale": 1.0, - "x": 0.191667, - "y": 0.772849, + "scale": 0.95, + "x": 0.1375, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 }, { - "type": "D_PAD", + "type": "STICK", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_DPAD_UP", - "GAMEPAD_DPAD_RIGHT", - "GAMEPAD_DPAD_DOWN", - "GAMEPAD_DPAD_LEFT" + "GAMEPAD_LEFT_THUMB_UP", + "GAMEPAD_LEFT_THUMB_RIGHT", + "GAMEPAD_LEFT_THUMB_DOWN", + "GAMEPAD_LEFT_THUMB_LEFT" ], - "scale": 0.95, - "x": 0.125417, - "y": 0.477151, + "scale": 1.0, + "x": 0.1375, + "y": 0.833333, "toggleSwitch": false, "text": "", "iconId": 0 @@ -108,9 +108,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.372984, + "scale": 1.15, + "x": 0.8625, + "y": 0.393817, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.81, - "y": 0.477151, + "scale": 1.15, + "x": 0.79, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.939167, - "y": 0.477151, + "scale": 1.15, + "x": 0.935, + "y": 0.510753, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.0, - "x": 0.874583, - "y": 0.581317, + "scale": 1.15, + "x": 0.8625, + "y": 0.627688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -167,14 +167,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_L3", + "GAMEPAD_BUTTON_R3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.933333, - "y": 0.880376, + "scale": 1.15, + "x": 0.258333, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 @@ -183,14 +183,14 @@ "type": "BUTTON", "shape": "CIRCLE", "bindings": [ - "GAMEPAD_BUTTON_R3", + "GAMEPAD_BUTTON_L3", "NONE", "NONE", "NONE" ], - "scale": 1.0, - "x": 0.066667, - "y": 0.880376, + "scale": 1.15, + "x": 0.741667, + "y": 0.705645, "toggleSwitch": false, "text": "", "iconId": 0 From 817d6d3612e36188401020ebc16055cb12dc71c0 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 14:36:55 -0400 Subject: [PATCH 04/10] Stop a held button from acquiring a second button when the finger slides Sticky press kept a button down when the finger drifted off it, but the move handler still looked for another swipe target under that finger and pressed it as well. Holding RT and drifting onto RB therefore ended up holding both, which is worse than the drop it replaced: the finger never left the screen, so there was no moment at which the player asked for the second press. A pointer that already holds an element no longer acquires anything else. The element stays down until the pointer actually lifts, and a second button needs a genuine release and a fresh press. The branch removed here was the only path that could add a capture mid-gesture for a BUTTON. What this does not change: overlapping elements still both fire when the finger lands on them together, since that press is deliberate; several fingers still hold several buttons independently; and the D-pad and radial-menu hand-off is untouched, because it is gated on the held element being a D_PAD or RADIAL_MENU and is how a directional swipe is meant to work. --- .../runtime/input/ui/InputControlsView.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/src/main/runtime/input/ui/InputControlsView.java b/app/src/main/runtime/input/ui/InputControlsView.java index ebb609891..4236476d9 100644 --- a/app/src/main/runtime/input/ui/InputControlsView.java +++ b/app/src/main/runtime/input/ui/InputControlsView.java @@ -960,23 +960,6 @@ public boolean onTouchEvent(MotionEvent event) { if (handedOff) continue; if (activeElement.handleTouchMove(movePointerId, x, y)) pointerHandled = true; - - if (swipeAllowed - && activeElement.getType() == ControlElement.Type.BUTTON - && activeElement.isSwipeTarget() - && !activeElement.containsPoint(x, y)) { - for (ControlElement element : profile.getElements()) { - if (element == activeElement) continue; - if (capturesFor(movePointerId).contains(element)) continue; - if (element.isSwipeTarget() && element.handleTouchDown(movePointerId, x, y)) { - addCapture(movePointerId, element); - pointerHandled = true; - capturesChanged = true; - if (hapticsEnabled) triggerTouchHaptic(); - break; - } - } - } } if (!pointerHandled && snapshot.length == 0) { From 1583ee7ae96d4b6bbe9e2c6f8e2b72a66585015d Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 15:19:31 -0400 Subject: [PATCH 05/10] Tighten the face-button cluster to the measured thumb rest The diamond had been widened to 20.1 mm between adjacent buttons on the grounds that a thumb contact patch is about 25 mm across, so independent presses needed 20 mm of separation. That reasoning does not apply to this engine: hit testing compares the touch CENTROID against bounding boxes, so contact width cannot cause a false press. The buttons were spread for no benefit and the cluster was reported as too far apart to use. The constraint that does bind is that bounding boxes are SQUARE. On a diamond, diagonally adjacent boxes only clear when the radius is at least twice the button half-size, so larger buttons force wider spacing - which is why the stock phone layout pairs 11.7 mm buttons with 17.9 mm spacing. Compressing the diamond vertically into a rhombus, 164 px wide by 110 px tall, keeps the boxes clear at full button size. Vertical is the right axis to give up: the thumb moves measurably slower in flexion and extension than in adduction and abduction, so shortening the up-and-down hops costs the least. The cluster centre was then placed to minimise the farthest button from the thumb rest measured off the digitiser at 33 mm inboard and 35 mm up. adjacent travel 20.1 -> 16.1 mm farthest from the rest 40.5 -> 27.8 mm average from the rest 28.3 -> 21.3 mm button size 13.3 mm, unchanged A now sits 11.2 mm from where the thumb rests. The D-pad mirrors the new centre, which brings it from 26.5 to 19.2 mm on the left. The layout check was wrong too. Its 4 mm edge-gap floor was invented rather than derived, and it is the wrong test for a single cluster of related buttons. Face button pairs are now checked on what actually matters: bounding boxes must not overlap, since co-press would otherwise fire two buttons from one touch, and centres must be at least 15 mm apart so a mis-hit needs more than a 2.5 sigma targeting error. The visible gap is 2.8 mm, which is tight to look at and safe by that test. --- .../profiles-astra2/controls-2.icp | 14 +++++++------- .../profiles-astra2/controls-3.icp | 18 +++++++++--------- .../profiles-astra2/controls-6.icp | 18 +++++++++--------- .../profiles-astra2/controls-7.icp | 18 +++++++++--------- .../profiles-astra2/controls-8.icp | 18 +++++++++--------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp index eb2811103..853cb9671 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -93,8 +93,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.393817, + "x": 0.866667, + "y": 0.500672, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "NONE" ], "scale": 1.15, - "x": 0.79, - "y": 0.510753, + "x": 0.798333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -126,7 +126,7 @@ ], "scale": 1.15, "x": 0.935, - "y": 0.510753, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.627688, + "x": 0.866667, + "y": 0.648522, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index 94ce4b748..57f762e3f 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -77,8 +77,8 @@ "GAMEPAD_DPAD_LEFT" ], "scale": 0.85, - "x": 0.1375, - "y": 0.510753, + "x": 0.133333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.393817, + "x": 0.866667, + "y": 0.500672, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.15, - "x": 0.79, - "y": 0.510753, + "x": 0.798333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -158,7 +158,7 @@ ], "scale": 1.15, "x": 0.935, - "y": 0.510753, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -173,8 +173,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.627688, + "x": 0.866667, + "y": 0.648522, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index 3531c6c03..aed904ee3 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -79,8 +79,8 @@ "NONE" ], "scale": 1.15, - "x": 0.1375, - "y": 0.510753, + "x": 0.133333, + "y": 0.574597, "toggleSwitch": true, "text": "", "iconId": 0 @@ -95,8 +95,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.393817, + "x": 0.866667, + "y": 0.500672, "toggleSwitch": false, "text": "", "iconId": 0 @@ -111,8 +111,8 @@ "NONE" ], "scale": 1.15, - "x": 0.79, - "y": 0.510753, + "x": 0.798333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -128,7 +128,7 @@ ], "scale": 1.15, "x": 0.935, - "y": 0.510753, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -143,8 +143,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.627688, + "x": 0.866667, + "y": 0.648522, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp index da9a1f5a5..edfd8dbc0 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -77,8 +77,8 @@ "GAMEPAD_DPAD_LEFT" ], "scale": 0.85, - "x": 0.1375, - "y": 0.510753, + "x": 0.133333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.393817, + "x": 0.866667, + "y": 0.500672, "toggleSwitch": false, "text": "", "iconId": 0 @@ -141,8 +141,8 @@ "NONE" ], "scale": 1.15, - "x": 0.79, - "y": 0.510753, + "x": 0.798333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -158,7 +158,7 @@ ], "scale": 1.15, "x": 0.935, - "y": 0.510753, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -173,8 +173,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.627688, + "x": 0.866667, + "y": 0.648522, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp index 099921d90..5ce011485 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -77,8 +77,8 @@ "GAMEPAD_DPAD_LEFT" ], "scale": 0.95, - "x": 0.1375, - "y": 0.510753, + "x": 0.133333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.393817, + "x": 0.866667, + "y": 0.500672, "toggleSwitch": false, "text": "", "iconId": 0 @@ -125,8 +125,8 @@ "NONE" ], "scale": 1.15, - "x": 0.79, - "y": 0.510753, + "x": 0.798333, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -142,7 +142,7 @@ ], "scale": 1.15, "x": 0.935, - "y": 0.510753, + "y": 0.574597, "toggleSwitch": false, "text": "", "iconId": 0 @@ -157,8 +157,8 @@ "NONE" ], "scale": 1.15, - "x": 0.8625, - "y": 0.627688, + "x": 0.866667, + "y": 0.648522, "toggleSwitch": false, "text": "", "iconId": 0 From 6ed27a0858de20e20ff4dbe481fafd3e5b2634ad Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 15:24:35 -0400 Subject: [PATCH 06/10] Bump the asset sync revision so new bundled layouts actually install Changing the contents of a bundled .icp does nothing on its own. The sync gate is oldVersion == newVersion && oldSyncRevision >= REVISION && !deviceChanged and a debug rebuild changes none of those three, so copyAssetProfilesIfNeeded returns before the copy loop. The tightened face-button cluster was committed, built, signed and installed, and the layouts on disk still read 20.1 mm between adjacent buttons - the new assets never left the APK. The revision goes to 10. Verified on device: the pref advanced to 10 and all five bundled layouts carrying a face-button cluster now read 16.1 mm. Any future edit to a file under assets/inputcontrols/profiles* has to bump this constant in the same commit or it ships to nobody. --- app/src/main/runtime/input/controls/InputControlsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index 13ae2a574..c0d4064cb 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -26,7 +26,7 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 9; + private static final int ASSET_PROFILE_SYNC_REVISION = 10; private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; From 70be852b37a517f072cc85d6284d60c7be70e050 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 15:33:12 -0400 Subject: [PATCH 07/10] Lay the Astra 2 gamepad out as a DualSense half on the measured thumb rest The previous passes optimised every control to sit as close to the thumb as possible. That is not how a controller feels: on a real pad the thumb rests ON the stick and the face buttons are a deliberate reach up and out from it. The result had looked scattered rather than like a controller, and the sticks sat 13 mm below and 6 mm outboard of where the thumbs were measured to rest. Each half is now built as a DualSense half scaled to the panel: stick on the measured rest 3.6 mm from it, was 14.6 cluster centre from the stick 34.8 mm (DualSense ~39) far button from the stick 47.0 mm (DualSense ~55) face buttons 12.2 mm at 17.5 mm, a perfect diamond D-pad 26 mm, mirroring the cluster centre A DualSense puts the cluster ~22 mm outboard of the stick. With the thumb rest only 33 mm from the panel edge the cluster can move ~7 mm outboard before the far button leaves the screen, so it goes up instead and every other proportion is kept. The diamond is square again rather than the squashed rhombus of the last revision, at r = 152 px with 75 px buttons, which keeps the square hit boxes clear without shrinking the buttons below the thumb plateau. L3 and R3 move to the gap between stick and cluster, up-inboard of the stick. That is inside 25 mm of the measured rest and inside 60 mm from every candidate grip, where the previous stick-height slot failed the low grip at 62 mm. Every pair mirrors exactly: sticks, triggers, bumpers, L3/R3, Select/Start, and the D-pad against the cluster centre, at identical scale on both sides. Asset sync revision goes to 11 so the layouts install. --- .../profiles-astra2/controls-2.icp | 42 +++++++-------- .../profiles-astra2/controls-3.icp | 50 +++++++++--------- .../profiles-astra2/controls-6.icp | 52 +++++++++---------- .../profiles-astra2/controls-7.icp | 50 +++++++++--------- .../profiles-astra2/controls-8.icp | 44 ++++++++-------- .../input/controls/InputControlsManager.java | 2 +- 6 files changed, 120 insertions(+), 120 deletions(-) diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp index 853cb9671..4be152e97 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -76,9 +76,9 @@ "KEY_S", "KEY_A" ], - "scale": 0.85, - "x": 0.1375, - "y": 0.833333, + "scale": 0.95, + "x": 0.166667, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -92,9 +92,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.500672, + "scale": 1.05, + "x": 0.870833, + "y": 0.370968, "toggleSwitch": false, "text": "", "iconId": 0 @@ -108,9 +108,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.798333, - "y": 0.574597, + "scale": 1.05, + "x": 0.8075, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.935, - "y": 0.574597, + "scale": 1.05, + "x": 0.934167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.648522, + "scale": 1.05, + "x": 0.870833, + "y": 0.575269, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.258333, - "y": 0.705645, + "scale": 1.05, + "x": 0.270833, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.741667, - "y": 0.705645, + "scale": 1.05, + "x": 0.729167, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index 57f762e3f..ad05d9eca 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -76,9 +76,9 @@ "GAMEPAD_DPAD_DOWN", "GAMEPAD_DPAD_LEFT" ], - "scale": 0.85, - "x": 0.133333, - "y": 0.574597, + "scale": 0.95, + "x": 0.129167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -93,8 +93,8 @@ "GAMEPAD_LEFT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.1375, - "y": 0.833333, + "x": 0.166667, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.8625, - "y": 0.833333, + "x": 0.833333, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.500672, + "scale": 1.05, + "x": 0.870833, + "y": 0.370968, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.798333, - "y": 0.574597, + "scale": 1.05, + "x": 0.8075, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.935, - "y": 0.574597, + "scale": 1.05, + "x": 0.934167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.648522, + "scale": 1.05, + "x": 0.870833, + "y": 0.575269, "toggleSwitch": false, "text": "", "iconId": 0 @@ -188,9 +188,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.258333, - "y": 0.705645, + "scale": 1.05, + "x": 0.270833, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 @@ -204,9 +204,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.741667, - "y": 0.705645, + "scale": 1.05, + "x": 0.729167, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index aed904ee3..df1baf65c 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -78,9 +78,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.133333, - "y": 0.574597, + "scale": 1.05, + "x": 0.129167, + "y": 0.473118, "toggleSwitch": true, "text": "", "iconId": 0 @@ -94,9 +94,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.500672, + "scale": 1.05, + "x": 0.870833, + "y": 0.370968, "toggleSwitch": false, "text": "", "iconId": 0 @@ -110,9 +110,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.798333, - "y": 0.574597, + "scale": 1.05, + "x": 0.8075, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -126,9 +126,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.935, - "y": 0.574597, + "scale": 1.05, + "x": 0.934167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -142,9 +142,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.648522, + "scale": 1.05, + "x": 0.870833, + "y": 0.575269, "toggleSwitch": false, "text": "", "iconId": 0 @@ -159,8 +159,8 @@ "KEY_A" ], "scale": 1.0, - "x": 0.1375, - "y": 0.833333, + "x": 0.166667, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -175,8 +175,8 @@ "NONE" ], "scale": 1.0, - "x": 0.8625, - "y": 0.833333, + "x": 0.833333, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -190,9 +190,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.258333, - "y": 0.705645, + "scale": 1.05, + "x": 0.270833, + "y": 0.645161, "toggleSwitch": true, "text": "", "iconId": 0 @@ -206,9 +206,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.741667, - "y": 0.705645, + "scale": 1.05, + "x": 0.729167, + "y": 0.645161, "toggleSwitch": true, "text": "", "iconId": 0 @@ -222,7 +222,7 @@ "NONE", "NONE" ], - "scale": 1.15, + "scale": 1.05, "x": 0.6875, "y": 0.880376, "toggleSwitch": false, diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp index edfd8dbc0..710024146 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -76,9 +76,9 @@ "GAMEPAD_DPAD_DOWN", "GAMEPAD_DPAD_LEFT" ], - "scale": 0.85, - "x": 0.133333, - "y": 0.574597, + "scale": 0.95, + "x": 0.129167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -93,8 +93,8 @@ "GAMEPAD_LEFT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.1375, - "y": 0.833333, + "x": 0.166667, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -109,8 +109,8 @@ "GAMEPAD_RIGHT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.8625, - "y": 0.833333, + "x": 0.833333, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.500672, + "scale": 1.05, + "x": 0.870833, + "y": 0.370968, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.798333, - "y": 0.574597, + "scale": 1.05, + "x": 0.8075, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.935, - "y": 0.574597, + "scale": 1.05, + "x": 0.934167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.648522, + "scale": 1.05, + "x": 0.870833, + "y": 0.575269, "toggleSwitch": false, "text": "", "iconId": 0 @@ -188,9 +188,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.258333, - "y": 0.705645, + "scale": 1.05, + "x": 0.270833, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 @@ -204,9 +204,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.741667, - "y": 0.705645, + "scale": 1.05, + "x": 0.729167, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp index 5ce011485..4f3fcead1 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -77,8 +77,8 @@ "GAMEPAD_DPAD_LEFT" ], "scale": 0.95, - "x": 0.133333, - "y": 0.574597, + "x": 0.129167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -93,8 +93,8 @@ "GAMEPAD_LEFT_THUMB_LEFT" ], "scale": 1.0, - "x": 0.1375, - "y": 0.833333, + "x": 0.166667, + "y": 0.752688, "toggleSwitch": false, "text": "", "iconId": 0 @@ -108,9 +108,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.500672, + "scale": 1.05, + "x": 0.870833, + "y": 0.370968, "toggleSwitch": false, "text": "", "iconId": 0 @@ -124,9 +124,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.798333, - "y": 0.574597, + "scale": 1.05, + "x": 0.8075, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -140,9 +140,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.935, - "y": 0.574597, + "scale": 1.05, + "x": 0.934167, + "y": 0.473118, "toggleSwitch": false, "text": "", "iconId": 0 @@ -156,9 +156,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.866667, - "y": 0.648522, + "scale": 1.05, + "x": 0.870833, + "y": 0.575269, "toggleSwitch": false, "text": "", "iconId": 0 @@ -172,9 +172,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.258333, - "y": 0.705645, + "scale": 1.05, + "x": 0.270833, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 @@ -188,9 +188,9 @@ "NONE", "NONE" ], - "scale": 1.15, - "x": 0.741667, - "y": 0.705645, + "scale": 1.05, + "x": 0.729167, + "y": 0.645161, "toggleSwitch": false, "text": "", "iconId": 0 diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index c0d4064cb..17676e945 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -26,7 +26,7 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 10; + private static final int ASSET_PROFILE_SYNC_REVISION = 11; private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; From 7e24f4a5c862b09d92e71cbcff834d58ae3a61ad Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 15:36:45 -0400 Subject: [PATCH 08/10] Put the Astra 2 triggers and bumpers back in the top corners The triggers had been pulled 12 mm inboard, to x = 380 / 2020, on the theory that an index finger curling over the top-right corner could hit the tablet's Magic Key and drop into Game Space mid-game. In use they were reported as too far in and awkward to reach. An index finger wrapping the top edge lands nearest the corner it wraps, so that is where the buttons belong; the Magic Key concern was hypothetical and the person holding the device is the better judge. LT/LB and RT/RB move to x = 230 / 2170. The pair now spans 9 to 28 mm in from its side edge instead of 21 to 40. Vertical placement is unchanged: the upper button 6.5 to 16 mm from the top, the lower 21 to 30 mm. Every other element is where the last revision put it. Asset sync revision goes to 12 so the change installs. --- .../assets/inputcontrols/profiles-astra2/controls-2.icp | 8 ++++---- .../assets/inputcontrols/profiles-astra2/controls-3.icp | 8 ++++---- .../assets/inputcontrols/profiles-astra2/controls-6.icp | 4 ++-- .../assets/inputcontrols/profiles-astra2/controls-7.icp | 8 ++++---- .../assets/inputcontrols/profiles-astra2/controls-8.icp | 8 ++++---- .../main/runtime/input/controls/InputControlsManager.java | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp index 4be152e97..b8fbc6f25 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-2.icp @@ -13,7 +13,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -29,7 +29,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -45,7 +45,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.212366, "toggleSwitch": false, "text": "", @@ -61,7 +61,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.212366, "toggleSwitch": false, "text": "", diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp index ad05d9eca..1b2734eff 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-3.icp @@ -13,7 +13,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -29,7 +29,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -45,7 +45,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.212366, "toggleSwitch": false, "text": "", @@ -61,7 +61,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.212366, "toggleSwitch": false, "text": "", diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index df1baf65c..eeee11d71 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -47,7 +47,7 @@ "KEY_M" ], "scale": 1.1, - "x": 0.158333, + "x": 0.095833, "y": 0.241935, "toggleSwitch": false, "text": "", @@ -63,7 +63,7 @@ "KEY_T" ], "scale": 1.1, - "x": 0.841667, + "x": 0.904167, "y": 0.241935, "toggleSwitch": false, "text": "", diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp index 710024146..a27877e59 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-7.icp @@ -13,7 +13,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -29,7 +29,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -45,7 +45,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.212366, "toggleSwitch": false, "text": "", @@ -61,7 +61,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.212366, "toggleSwitch": false, "text": "", diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp index 4f3fcead1..57a28e79e 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-8.icp @@ -13,7 +13,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -29,7 +29,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.091398, "toggleSwitch": false, "text": "", @@ -45,7 +45,7 @@ "NONE" ], "scale": 1.2, - "x": 0.158333, + "x": 0.095833, "y": 0.212366, "toggleSwitch": false, "text": "", @@ -61,7 +61,7 @@ "NONE" ], "scale": 1.2, - "x": 0.841667, + "x": 0.904167, "y": 0.212366, "toggleSwitch": false, "text": "", diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index 17676e945..ad996525b 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -26,7 +26,7 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 11; + private static final int ASSET_PROFILE_SYNC_REVISION = 12; private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; From 4530f28138a2913e07de95d17d6bc29697bc7fe0 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 16:15:17 -0400 Subject: [PATCH 09/10] Drive library card shape and drawer button size from the device profile Two more of the Device Profile's promised surfaces are wired up. Library artwork. The grid capped every card at 1.25 times its width - a portrait card - and drew the primary artwork into it with ContentScale.Crop. Steam's primary slot resolves to the 460x215 header, so a 2.14:1 landscape image was centre-cropped into a 0.8:1 card and lost most of its width; itch covers are 630x500 and Epic's wide banner and GOG's hero art are 16:9, all landscape too. The Astra 2 profile now asks for 3:2 cards (height 0.67 of width; the title strip takes the rest, leaving the image close to 16:9), three columns in landscape and two in portrait, and a wide-artwork preference that picks Steam's header explicitly, Epic's DieselStoreFrontWide banner ahead of the tall box, and GOG's hero art ahead of the square tile. The Default profile keeps the stock 1.25 portrait card, the stock column ladder and the stock slot order, so phones are untouched. The carousel and list paths keep their own slots. Session drawer buttons. ActionCardGrid sized each card by dividing the leftover drawer height by the row count, floored at 72 dp. On a phone that leftover is small; on the Astra 2 the drawer is 668 dp tall, so six cards in two rows became about 220 dp tall by 88 dp wide. The row height now also respects a per-profile maximum aspect: Astra 2 caps cards at 1.0 so they are square (88 x 88 dp on the 300 dp drawer), Default keeps Float.MAX_VALUE and fills as before. The computation is a pure function so the geometry is unit-tested at the Astra 2 numbers rather than eyeballed. The Device page now reports both surfaces as live instead of Coming Soon, with four new strings in the base and all 22 locales. --- .../main/app/config/DeviceProfileSettings.kt | 72 ++++++++++++++++++- .../main/app/shell/UnifiedActivityStores.kt | 6 +- .../settings/device/DeviceProfileScreen.kt | 18 +++++ .../stores/common/StoreArtworkCache.kt | 21 ++++-- app/src/main/res/values-b+es+419/strings.xml | 4 ++ app/src/main/res/values-da/strings.xml | 4 ++ app/src/main/res/values-de/strings.xml | 4 ++ app/src/main/res/values-es/strings.xml | 4 ++ app/src/main/res/values-fi/strings.xml | 4 ++ app/src/main/res/values-fr/strings.xml | 4 ++ app/src/main/res/values-hi/strings.xml | 4 ++ app/src/main/res/values-it/strings.xml | 4 ++ app/src/main/res/values-ja/strings.xml | 4 ++ app/src/main/res/values-ko/strings.xml | 4 ++ app/src/main/res/values-no/strings.xml | 4 ++ app/src/main/res/values-pl/strings.xml | 4 ++ app/src/main/res/values-pt-rBR/strings.xml | 4 ++ app/src/main/res/values-pt/strings.xml | 4 ++ app/src/main/res/values-ro/strings.xml | 4 ++ app/src/main/res/values-ru/strings.xml | 4 ++ app/src/main/res/values-sv/strings.xml | 4 ++ app/src/main/res/values-th/strings.xml | 4 ++ app/src/main/res/values-tr/strings.xml | 4 ++ app/src/main/res/values-uk/strings.xml | 4 ++ app/src/main/res/values-zh-rCN/strings.xml | 4 ++ app/src/main/res/values-zh-rTW/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 4 ++ .../main/runtime/display/XServerDrawerMenu.kt | 19 ++++- app/src/main/shared/ui/FourByTwoGridView.kt | 24 ++++--- .../cmod/app/config/DeviceProfileTest.kt | 54 ++++++++++++++ 30 files changed, 286 insertions(+), 20 deletions(-) diff --git a/app/src/main/app/config/DeviceProfileSettings.kt b/app/src/main/app/config/DeviceProfileSettings.kt index d0f0f1d68..48fd33a49 100644 --- a/app/src/main/app/config/DeviceProfileSettings.kt +++ b/app/src/main/app/config/DeviceProfileSettings.kt @@ -22,12 +22,12 @@ enum class DeviceProfileFeature( LIBRARY_ICONS( R.string.device_profile_feature_library_icons, R.string.device_profile_feature_library_icons_summary, - false, + true, ), SESSION_MENU_SIZES( R.string.device_profile_feature_session_menu, R.string.device_profile_feature_session_menu_summary, - false, + true, ), SHORTCUT_RECOMMENDATIONS( R.string.device_profile_feature_shortcut_defaults, @@ -94,4 +94,72 @@ object DeviceProfileSettings { @JvmStatic fun adaptiveJoysticksDefaultExtra(context: Context): String = if (adaptiveJoysticksDefault(context)) "1" else "0" + + @JvmStatic + fun preferWideArtwork(context: Context): Boolean = preferWideArtwork(current(context)) + + @JvmStatic + fun libraryCardHeightFactor( + context: Context, + portrait: Boolean, + ): Float = libraryCardHeightFactor(current(context), portrait) + + @JvmStatic + fun libraryColumns( + context: Context, + widthDp: Int, + portrait: Boolean, + ): Int = libraryColumns(current(context), widthDp, portrait) + + @JvmStatic + fun sessionActionCardMaxAspect(context: Context): Float = sessionActionCardMaxAspect(current(context)) + + internal fun preferWideArtwork(profile: DeviceProfile): Boolean = profile == DeviceProfile.ASTRA_2 + + internal fun libraryCardHeightFactor( + profile: DeviceProfile, + portrait: Boolean, + ): Float = + when (profile) { + DeviceProfile.ASTRA_2 -> 0.67f + DeviceProfile.DEFAULT -> 1.25f + } + + internal fun libraryColumns( + profile: DeviceProfile, + widthDp: Int, + portrait: Boolean, + ): Int = + when (profile) { + DeviceProfile.ASTRA_2 -> if (portrait) 2 else 3 + DeviceProfile.DEFAULT -> stockLibraryColumns(widthDp) + } + + internal fun stockLibraryColumns(widthDp: Int): Int = + when { + widthDp <= 0 -> 4 + widthDp < 480 -> 2 + widthDp < 700 -> 3 + else -> 4 + } + + internal fun sessionActionCardMaxAspect(profile: DeviceProfile): Float = + when (profile) { + DeviceProfile.ASTRA_2 -> 1.0f + DeviceProfile.DEFAULT -> Float.MAX_VALUE + } + + internal fun actionCardRowHeight( + availableHeight: Float, + cardWidth: Float, + rows: Int, + spacing: Float, + minHeight: Float, + maxAspect: Float, + ): Float { + val safeRows = rows.coerceAtLeast(1) + val fill = (availableHeight - spacing * (safeRows - 1)) / safeRows + val capped = if (maxAspect == Float.MAX_VALUE) fill else minOf(fill, cardWidth * maxAspect) + return maxOf(capped, minHeight) + } } diff --git a/app/src/main/app/shell/UnifiedActivityStores.kt b/app/src/main/app/shell/UnifiedActivityStores.kt index 301ff1cba..f39da9a31 100644 --- a/app/src/main/app/shell/UnifiedActivityStores.kt +++ b/app/src/main/app/shell/UnifiedActivityStores.kt @@ -144,6 +144,7 @@ import androidx.navigation.navArgument import coil.compose.AsyncImage import coil.imageLoader import coil.request.ImageRequest +import com.winlator.cmod.app.config.DeviceProfileSettings import com.winlator.cmod.BuildConfig import com.winlator.cmod.R import com.winlator.cmod.app.PluviaApp @@ -435,11 +436,12 @@ internal fun UnifiedActivity.GameCapsule( } } } else { + val preferWide = !useLibraryCapsule && !listMode && DeviceProfileSettings.preferWideArtwork(context) val imageModel = - remember(app.id, gogGame, epicGame, useLibraryCapsule, listMode, artworkCacheRefreshKey) { + remember(app.id, gogGame, epicGame, useLibraryCapsule, listMode, preferWide, artworkCacheRefreshKey) { StoreArtworkCache.imageModel( context, - StoreArtworkCache.primaryRef(app, gogGame, epicGame, useLibraryCapsule, listMode), + StoreArtworkCache.primaryRef(app, gogGame, epicGame, useLibraryCapsule, listMode, preferWide), ) } AsyncImage( diff --git a/app/src/main/feature/settings/device/DeviceProfileScreen.kt b/app/src/main/feature/settings/device/DeviceProfileScreen.kt index 0ac88ead3..187953a1f 100644 --- a/app/src/main/feature/settings/device/DeviceProfileScreen.kt +++ b/app/src/main/feature/settings/device/DeviceProfileScreen.kt @@ -392,6 +392,22 @@ fun DeviceProfileScreen(bridge: SettingsNavBridge? = null) { R.string.device_profile_layouts_tuned }, ) + val libraryStatus = + stringResource( + if (DeviceProfileSettings.preferWideArtwork(context)) { + R.string.device_profile_library_wide + } else { + R.string.device_profile_library_stock + }, + ) + val sessionStatus = + stringResource( + if (DeviceProfileSettings.sessionActionCardMaxAspect(context) == Float.MAX_VALUE) { + R.string.device_profile_session_stock + } else { + R.string.device_profile_session_square + }, + ) CompositionLocalProvider(LocalPaneNav provides contentNav) { Column( @@ -441,6 +457,8 @@ fun DeviceProfileScreen(bridge: SettingsNavBridge? = null) { when (feature) { DeviceProfileFeature.INPUT_CONTROL_LAYOUTS -> layoutStatus DeviceProfileFeature.ADAPTIVE_JOYSTICKS -> adaptiveStatus + DeviceProfileFeature.LIBRARY_ICONS -> libraryStatus + DeviceProfileFeature.SESSION_MENU_SIZES -> sessionStatus else -> null }, portrait = portrait, diff --git a/app/src/main/feature/stores/common/StoreArtworkCache.kt b/app/src/main/feature/stores/common/StoreArtworkCache.kt index 14075b4d6..868f264f0 100644 --- a/app/src/main/feature/stores/common/StoreArtworkCache.kt +++ b/app/src/main/feature/stores/common/StoreArtworkCache.kt @@ -145,10 +145,11 @@ object StoreArtworkCache { epicGame: EpicGame?, useLibraryCapsule: Boolean, listMode: Boolean, + preferWide: Boolean = false, ): ArtworkRef? = when { - gogGame != null -> gogPrimaryRef(gogGame) - epicGame != null -> epicPrimaryRef(epicGame) + gogGame != null -> gogPrimaryRef(gogGame, preferWide) + epicGame != null -> epicPrimaryRef(epicGame, preferWide) app.id < 0 -> null else -> { val slot: String @@ -162,6 +163,10 @@ object StoreArtworkCache { slot = "library_capsule" url = app.getLibraryCapsuleUrl() } + preferWide -> { + slot = "header" + url = app.getHeaderImageUrl() + } else -> { slot = "capsule" url = app.getCapsuleUrl() @@ -191,8 +196,12 @@ object StoreArtworkCache { ArtworkRef("epic", game.id.toString(), "hero", game.artPortrait), ).filter { it.url.isNotBlank() } - fun epicPrimaryRef(game: EpicGame): ArtworkRef? = + fun epicPrimaryRef( + game: EpicGame, + preferWide: Boolean = false, + ): ArtworkRef? = when { + preferWide && game.artPortrait.isNotBlank() -> ArtworkRef("epic", game.id.toString(), "hero", game.artPortrait) game.artCover.isNotBlank() -> ArtworkRef("epic", game.id.toString(), "cover", game.artCover) game.artSquare.isNotBlank() -> ArtworkRef("epic", game.id.toString(), "square", game.artSquare) game.artLogo.isNotBlank() -> ArtworkRef("epic", game.id.toString(), "logo", game.artLogo) @@ -213,8 +222,12 @@ object StoreArtworkCache { ArtworkRef("gog", game.id, "icon", game.iconUrl), ).filter { it.url.isNotBlank() } - fun gogPrimaryRef(game: GOGGame): ArtworkRef? = + fun gogPrimaryRef( + game: GOGGame, + preferWide: Boolean = false, + ): ArtworkRef? = when { + preferWide && game.heroImageUrl.isNotBlank() -> ArtworkRef("gog", game.id, "hero", game.heroImageUrl) game.imageUrl.isNotBlank() -> ArtworkRef("gog", game.id, "cover", game.imageUrl) game.iconUrl.isNotBlank() -> ArtworkRef("gog", game.id, "icon", game.iconUrl) else -> null diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 3278cba91..69d76e577 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -1117,6 +1117,10 @@ Por ejemplo, META para la tecla META, \n Placa Fabricante Pantalla + Tarjetas anchas 3:2 para arte horizontal + Cuadrícula estándar + Botones de acción cuadrados + Tamaños estándar Velocidad del cursor diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 748a97418..c0880ad01 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -830,6 +830,10 @@ F.eks. META for META-tast, \n Hardwarekort Producent Skærm + Brede 3:2-kort til liggende grafik + Standardgitter + Kvadratiske handlingsknapper + Standardstørrelser Markørhastighed diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ff05c41b6..fd472dd30 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -830,6 +830,10 @@ Z. B. META für Meta-Taste, \n Platine Hersteller Bildschirm + Breite 3:2-Karten für Querformat-Artwork + Standardraster + Quadratische Aktionstasten + Standardgrößen Zeigergeschwindigkeit diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e5ba83c40..b86566cd2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -830,6 +830,10 @@ Ej. META para la tecla META, \n Placa Fabricante Pantalla + Tarjetas anchas 3:2 para arte horizontal + Cuadrícula estándar + Botones de acción cuadrados + Tamaños estándar Velocidad del cursor diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index c1ea9b471..ed6b5d1f8 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1117,6 +1117,10 @@ E.g. META for META-näppäin, \n Piirilevy Valmistaja Näyttö + Leveät 3:2-kortit vaakakuville + Vakioruudukko + Neliömäiset toimintopainikkeet + Vakiokoot Osoittimen nopeus diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 20becbde5..b278f7871 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -830,6 +830,10 @@ Par ex. META pour la touche META, \n Carte Fabricant Écran + Cartes larges 3:2 pour les visuels en paysage + Grille standard + Boutons d\'action carrés + Tailles standard Vitesse du curseur diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 822166408..f3c1ed249 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2712,4 +2712,8 @@ बोर्ड निर्माता स्क्रीन + लैंडस्केप आर्टवर्क के लिए चौड़े 3:2 कार्ड + स्टॉक ग्रिड + वर्गाकार एक्शन बटन + स्टॉक आकार diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5ca663187..1104cae22 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -830,6 +830,10 @@ Ad es. META per il tasto META, \n Scheda Produttore Schermo + Schede larghe 3:2 per artwork orizzontali + Griglia standard + Pulsanti di azione quadrati + Dimensioni standard Velocità cursore diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9ca5b6b9f..b4389e6d0 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1117,6 +1117,10 @@ ボード メーカー 画面 + 横長アートワーク向けの3:2ワイドカード + 標準グリッド + 正方形のアクションボタン + 標準サイズ カーソル速度 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index eaf5cf438..833ae40c6 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -830,6 +830,10 @@ 보드 제조사 화면 + 가로 아트워크용 3:2 와이드 카드 + 기본 그리드 + 정사각형 동작 버튼 + 기본 크기 커서 속도 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 8135f6fb0..ebcec3a90 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -1117,6 +1117,10 @@ F.eks. META for META-tast, \n Hovedkort Produsent Skjerm + Brede 3:2-kort for liggende grafikk + Standardrutenett + Kvadratiske handlingsknapper + Standardstørrelser Markørhastighet diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 18f33ff28..3f06c35aa 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -836,6 +836,10 @@ Np. META dla klawisza META, \n Płyta Producent Ekran + Szerokie karty 3:2 dla grafik poziomych + Siatka standardowa + Kwadratowe przyciski akcji + Rozmiary standardowe Prędkość kursora diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index e685df04d..a99803456 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -830,6 +830,10 @@ Ex. META para tecla META, \n Placa Fabricante Tela + Cartões largos 3:2 para arte em paisagem + Grade padrão + Botões de ação quadrados + Tamanhos padrão Velocidade do Cursor diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e63793917..782a1e3af 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1117,6 +1117,10 @@ Por ex. META para tecla META, \n Placa Fabricante Ecrã + Cartões largos 3:2 para arte horizontal + Grelha padrão + Botões de ação quadrados + Tamanhos padrão Velocidade do cursor diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 0c167cc9e..d13dbe763 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -830,6 +830,10 @@ De ex. META pentru tasta META, \n Placă Producător Ecran + Carduri late 3:2 pentru grafică orizontală + Grilă standard + Butoane de acțiune pătrate + Dimensiuni standard Viteza cursorului diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 793016d1a..7b32e648b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -902,6 +902,10 @@ Плата Производитель Экран + Широкие карточки 3:2 для горизонтальных обложек + Стандартная сетка + Квадратные кнопки действий + Стандартные размеры Скорость курсора diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 99ae2cd36..85efb70bc 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1117,6 +1117,10 @@ T.ex. META för META-tangent, \n Kretskort Tillverkare Skärm + Breda 3:2-kort för liggande grafik + Standardrutnät + Kvadratiska åtgärdsknappar + Standardstorlekar Markörhastighet diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 0463b4111..b58ea9554 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1117,6 +1117,10 @@ บอร์ด ผู้ผลิต หน้าจอ + การ์ดกว้าง 3:2 สำหรับภาพแนวนอน + ตารางมาตรฐาน + ปุ่มการทำงานทรงสี่เหลี่ยมจัตุรัส + ขนาดมาตรฐาน ความเร็วเคอร์เซอร์ diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 460f3eef8..8a6220eef 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1117,6 +1117,10 @@ E.g. META için META tuşu, \n Kart Üretici Ekran + Yatay görseller için geniş 3:2 kartlar + Standart ızgara + Kare eylem düğmeleri + Standart boyutlar İmleç Hızı diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index d0f5d0b59..44ea17fd3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -836,6 +836,10 @@ Плата Виробник Екран + Широкі картки 3:2 для горизонтальних обкладинок + Стандартна сітка + Квадратні кнопки дій + Стандартні розміри Швидкість курсора diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cde0a6e31..4cbbba823 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -830,6 +830,10 @@ 主板 制造商 屏幕 + 适合横向封面的 3:2 宽卡片 + 默认网格 + 方形操作按钮 + 默认尺寸 光标速度 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4457ba740..1328f1234 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -830,6 +830,10 @@ 主板 製造商 螢幕 + 適合橫向封面的 3:2 寬卡片 + 預設格線 + 方形操作按鈕 + 預設尺寸 游標速度 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07b889d55..82dc45924 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1265,6 +1265,10 @@ E.g. META for META key, \n Board Manufacturer Screen + Wide 3:2 cards for landscape artwork + Stock grid + Square action buttons + Stock sizes Cursor Speed diff --git a/app/src/main/runtime/display/XServerDrawerMenu.kt b/app/src/main/runtime/display/XServerDrawerMenu.kt index becdb870a..d5ed93795 100644 --- a/app/src/main/runtime/display/XServerDrawerMenu.kt +++ b/app/src/main/runtime/display/XServerDrawerMenu.kt @@ -1,5 +1,7 @@ package com.winlator.cmod.runtime.display +import androidx.compose.ui.platform.LocalContext +import com.winlator.cmod.app.config.DeviceProfileSettings import android.app.Activity import android.content.Context import androidx.compose.animation.AnimatedContent @@ -2112,17 +2114,28 @@ private fun ActionCardGrid( } val verticalPadding = (10f * paneScale).dp + val horizontalPadding = (10f * paneScale).dp + val maxAspect = DeviceProfileSettings.sessionActionCardMaxAspect(LocalContext.current) BoxWithConstraints(modifier = Modifier.fillMaxSize()) { val rows = ((cards.size + ActionCardColumns - 1) / ActionCardColumns).coerceAtLeast(1) + val cardWidth = + (maxWidth - horizontalPadding * 2 - ActionCardSpacing * (ActionCardColumns - 1)) / ActionCardColumns val rowHeight = - ((maxHeight - verticalPadding * 2 - ActionCardSpacing * (rows - 1)) / rows) - .coerceAtLeast(ActionCardMinHeight * paneScale) + DeviceProfileSettings + .actionCardRowHeight( + availableHeight = (maxHeight - verticalPadding * 2).value, + cardWidth = cardWidth.value, + rows = rows, + spacing = ActionCardSpacing.value, + minHeight = (ActionCardMinHeight * paneScale).value, + maxAspect = maxAspect, + ).dp Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(horizontal = (10f * paneScale).dp, vertical = verticalPadding), + .padding(horizontal = horizontalPadding, vertical = verticalPadding), ) { FlowRow( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/shared/ui/FourByTwoGridView.kt b/app/src/main/shared/ui/FourByTwoGridView.kt index 6ae1c234c..ff158724d 100644 --- a/app/src/main/shared/ui/FourByTwoGridView.kt +++ b/app/src/main/shared/ui/FourByTwoGridView.kt @@ -26,6 +26,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.winlator.cmod.app.config.DeviceProfileSettings +import com.winlator.cmod.shared.ui.layout.isPortraitLayout import com.winlator.cmod.shared.ui.layout.screenWidthDp import kotlinx.coroutines.delay import kotlinx.coroutines.flow.StateFlow @@ -33,16 +35,19 @@ import kotlin.math.abs enum class ViewMode { Grid } -fun gridColumnsForWidth(widthDp: Int): Int = - when { - widthDp <= 0 -> 4 - widthDp < 480 -> 2 - widthDp < 700 -> 3 - else -> 4 - } +fun gridColumnsForWidth(widthDp: Int): Int = DeviceProfileSettings.stockLibraryColumns(widthDp) + +@Composable +fun defaultGridColumns(): Int = + DeviceProfileSettings.libraryColumns( + LocalContext.current, + screenWidthDp().value.toInt(), + isPortraitLayout(), + ) @Composable -fun defaultGridColumns(): Int = gridColumnsForWidth(screenWidthDp().value.toInt()) +fun defaultGridCardHeightFactor(): Float = + DeviceProfileSettings.libraryCardHeightFactor(LocalContext.current, isPortraitLayout()) /** * Unified grid layout used by store tabs @@ -62,6 +67,7 @@ fun FourByTwoGridView( items: List, modifier: Modifier = Modifier, columns: Int = defaultGridColumns(), + cardHeightFactor: Float = defaultGridCardHeightFactor(), spacing: Dp = 12.dp, contentPadding: PaddingValues = PaddingValues(0.dp), gridState: LazyGridState = rememberLazyGridState(), @@ -94,7 +100,7 @@ fun FourByTwoGridView( val availableColumnWidth = ((maxWidth - horizontalInset - spacing * (effectiveColumns - 1).toFloat()) / effectiveColumns.toFloat()) .coerceAtLeast(1.dp) - val targetRowHeight = minOf(availableRowHeight, availableColumnWidth * 1.25f) + val targetRowHeight = minOf(availableRowHeight, availableColumnWidth * cardHeightFactor) val rowHeight by animateDpAsState( targetValue = targetRowHeight, animationSpec = diff --git a/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt index 6e1ac2929..d81e3b4ea 100644 --- a/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt +++ b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt @@ -80,4 +80,58 @@ class DeviceProfileTest { assertEquals("", DeviceProfile.DEFAULT.assetToken) assertEquals("astra2", DeviceProfile.ASTRA_2.assetToken) } + + @Test + fun astra2PrefersWideArtworkAndDefaultDoesNot() { + assertEquals(true, DeviceProfileSettings.preferWideArtwork(DeviceProfile.ASTRA_2)) + assertEquals(false, DeviceProfileSettings.preferWideArtwork(DeviceProfile.DEFAULT)) + } + + @Test + fun astra2LibraryCardsAreWideAndDefaultStaysPortrait() { + assertEquals(0.67f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.ASTRA_2, portrait = false)) + assertEquals(0.67f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.ASTRA_2, portrait = true)) + assertEquals(1.25f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.DEFAULT, portrait = false)) + } + + @Test + fun astra2LibraryColumnsFollowOrientation() { + assertEquals(3, DeviceProfileSettings.libraryColumns(DeviceProfile.ASTRA_2, 1067, portrait = false)) + assertEquals(2, DeviceProfileSettings.libraryColumns(DeviceProfile.ASTRA_2, 668, portrait = true)) + } + + @Test + fun defaultLibraryColumnsKeepTheStockLadder() { + assertEquals(2, DeviceProfileSettings.libraryColumns(DeviceProfile.DEFAULT, 400, portrait = true)) + assertEquals(3, DeviceProfileSettings.libraryColumns(DeviceProfile.DEFAULT, 600, portrait = false)) + assertEquals(4, DeviceProfileSettings.libraryColumns(DeviceProfile.DEFAULT, 914, portrait = false)) + } + + @Test + fun astra2ActionCardsAreSquareOnTheTallDrawer() { + val cardWidth = (300f - 20f - 16f) / 3f + val tall = DeviceProfileSettings.actionCardRowHeight( + availableHeight = 450f, cardWidth = cardWidth, rows = 2, spacing = 8f, minHeight = 72f, + maxAspect = DeviceProfileSettings.sessionActionCardMaxAspect(DeviceProfile.ASTRA_2), + ) + assertEquals(cardWidth, tall, 0.01f) + } + + @Test + fun defaultActionCardsStillFillTheDrawer() { + val cardWidth = (300f - 20f - 16f) / 3f + val fill = DeviceProfileSettings.actionCardRowHeight( + availableHeight = 450f, cardWidth = cardWidth, rows = 2, spacing = 8f, minHeight = 72f, + maxAspect = DeviceProfileSettings.sessionActionCardMaxAspect(DeviceProfile.DEFAULT), + ) + assertEquals((450f - 8f) / 2f, fill, 0.01f) + } + + @Test + fun actionCardsNeverDropBelowTheMinimumHeight() { + val h = DeviceProfileSettings.actionCardRowHeight( + availableHeight = 100f, cardWidth = 40f, rows = 3, spacing = 8f, minHeight = 72f, maxAspect = 1.0f, + ) + assertEquals(72f, h, 0.01f) + } } From acbf6cb68c759b9f8214ddfb31b1a9c5e97cd95c Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 16:21:55 -0400 Subject: [PATCH 10/10] Size library cards from the artwork aspect and fit four across on the Astra 2 The 0.67 card factor was a guess at "3:2 card minus a title strip" and it left the image box at roughly 1.8:1. Steam headers are 460x215, 2.14:1, so with ContentScale.Crop the sides went - "RAGON'S DOGM", "ONSTER HUNTER". The grid now derives the row height from the image itself: column width divided by the profile's image aspect, plus the 24 dp title strip (4 dp padding, one bodySmall line, 4 dp padding). The Astra 2 profile's aspect is the Steam header exactly, so headers fit edge to edge; Epic's wide banner and GOG's hero art lose a thin band top and bottom. The Default profile reports no aspect and keeps the stock 1.25 card factor. Landscape goes from three columns to four. On the 1067 dp panel that is about 245 dp per column and a 138 dp card, which fits three and a half rows in view. Portrait stays at two. --- .../main/app/config/DeviceProfileSettings.kt | 22 +++++++++---------- app/src/main/shared/ui/FourByTwoGridView.kt | 22 +++++++++++++++---- .../cmod/app/config/DeviceProfileTest.kt | 9 ++++---- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/app/src/main/app/config/DeviceProfileSettings.kt b/app/src/main/app/config/DeviceProfileSettings.kt index 48fd33a49..80643ffcc 100644 --- a/app/src/main/app/config/DeviceProfileSettings.kt +++ b/app/src/main/app/config/DeviceProfileSettings.kt @@ -39,6 +39,9 @@ enum class DeviceProfileFeature( object DeviceProfileSettings { const val KEY_PROFILE = "device_profile" const val KEY_DETECTED = "device_profile_detected" + const val STEAM_HEADER_ASPECT = 460f / 215f + const val LIBRARY_TITLE_STRIP_DP = 24f + const val STOCK_LIBRARY_CARD_FACTOR = 1.25f @Volatile private var cached: DeviceProfile? = null @@ -99,10 +102,10 @@ object DeviceProfileSettings { fun preferWideArtwork(context: Context): Boolean = preferWideArtwork(current(context)) @JvmStatic - fun libraryCardHeightFactor( - context: Context, - portrait: Boolean, - ): Float = libraryCardHeightFactor(current(context), portrait) + fun libraryImageAspect(context: Context): Float? = libraryImageAspect(current(context)) + + @JvmStatic + fun libraryTitleStripDp(): Float = LIBRARY_TITLE_STRIP_DP @JvmStatic fun libraryColumns( @@ -116,13 +119,10 @@ object DeviceProfileSettings { internal fun preferWideArtwork(profile: DeviceProfile): Boolean = profile == DeviceProfile.ASTRA_2 - internal fun libraryCardHeightFactor( - profile: DeviceProfile, - portrait: Boolean, - ): Float = + internal fun libraryImageAspect(profile: DeviceProfile): Float? = when (profile) { - DeviceProfile.ASTRA_2 -> 0.67f - DeviceProfile.DEFAULT -> 1.25f + DeviceProfile.ASTRA_2 -> STEAM_HEADER_ASPECT + DeviceProfile.DEFAULT -> null } internal fun libraryColumns( @@ -131,7 +131,7 @@ object DeviceProfileSettings { portrait: Boolean, ): Int = when (profile) { - DeviceProfile.ASTRA_2 -> if (portrait) 2 else 3 + DeviceProfile.ASTRA_2 -> if (portrait) 2 else 4 DeviceProfile.DEFAULT -> stockLibraryColumns(widthDp) } diff --git a/app/src/main/shared/ui/FourByTwoGridView.kt b/app/src/main/shared/ui/FourByTwoGridView.kt index ff158724d..293d455a5 100644 --- a/app/src/main/shared/ui/FourByTwoGridView.kt +++ b/app/src/main/shared/ui/FourByTwoGridView.kt @@ -46,8 +46,18 @@ fun defaultGridColumns(): Int = ) @Composable -fun defaultGridCardHeightFactor(): Float = - DeviceProfileSettings.libraryCardHeightFactor(LocalContext.current, isPortraitLayout()) +fun defaultGridImageAspect(): Float? = DeviceProfileSettings.libraryImageAspect(LocalContext.current) + +fun gridRowHeightFor( + columnWidth: Dp, + imageAspect: Float?, + titleStripDp: Float, +): Dp = + if (imageAspect == null || imageAspect <= 0f) { + columnWidth * DeviceProfileSettings.STOCK_LIBRARY_CARD_FACTOR + } else { + columnWidth / imageAspect + titleStripDp.dp + } /** * Unified grid layout used by store tabs @@ -67,7 +77,7 @@ fun FourByTwoGridView( items: List, modifier: Modifier = Modifier, columns: Int = defaultGridColumns(), - cardHeightFactor: Float = defaultGridCardHeightFactor(), + imageAspect: Float? = defaultGridImageAspect(), spacing: Dp = 12.dp, contentPadding: PaddingValues = PaddingValues(0.dp), gridState: LazyGridState = rememberLazyGridState(), @@ -100,7 +110,11 @@ fun FourByTwoGridView( val availableColumnWidth = ((maxWidth - horizontalInset - spacing * (effectiveColumns - 1).toFloat()) / effectiveColumns.toFloat()) .coerceAtLeast(1.dp) - val targetRowHeight = minOf(availableRowHeight, availableColumnWidth * cardHeightFactor) + val targetRowHeight = + minOf( + availableRowHeight, + gridRowHeightFor(availableColumnWidth, imageAspect, DeviceProfileSettings.libraryTitleStripDp()), + ) val rowHeight by animateDpAsState( targetValue = targetRowHeight, animationSpec = diff --git a/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt index d81e3b4ea..d02ab4feb 100644 --- a/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt +++ b/app/src/test/kotlin/com/winlator/cmod/app/config/DeviceProfileTest.kt @@ -88,15 +88,14 @@ class DeviceProfileTest { } @Test - fun astra2LibraryCardsAreWideAndDefaultStaysPortrait() { - assertEquals(0.67f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.ASTRA_2, portrait = false)) - assertEquals(0.67f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.ASTRA_2, portrait = true)) - assertEquals(1.25f, DeviceProfileSettings.libraryCardHeightFactor(DeviceProfile.DEFAULT, portrait = false)) + fun astra2LibraryImagesMatchTheSteamHeaderAndDefaultIsStock() { + assertEquals(460f / 215f, DeviceProfileSettings.libraryImageAspect(DeviceProfile.ASTRA_2)!!, 0.001f) + assertEquals(null, DeviceProfileSettings.libraryImageAspect(DeviceProfile.DEFAULT)) } @Test fun astra2LibraryColumnsFollowOrientation() { - assertEquals(3, DeviceProfileSettings.libraryColumns(DeviceProfile.ASTRA_2, 1067, portrait = false)) + assertEquals(4, DeviceProfileSettings.libraryColumns(DeviceProfile.ASTRA_2, 1067, portrait = false)) assertEquals(2, DeviceProfileSettings.libraryColumns(DeviceProfile.ASTRA_2, 668, portrait = true)) }