diff --git a/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt b/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt index bd0ea12a56..fecc912f71 100644 --- a/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt +++ b/app/src/main/java/app/gamenative/ui/component/QuickMenu.kt @@ -46,6 +46,7 @@ import androidx.compose.material.icons.filled.Gamepad import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.QueryStats import androidx.compose.material.icons.filled.TouchApp +import androidx.compose.material.icons.filled.GpsFixed import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator @@ -91,6 +92,7 @@ object QuickMenuAction { const val EDIT_CONTROLS = 4 const val EDIT_PHYSICAL_CONTROLLER = 5 const val PERFORMANCE_HUD = 6 + const val GYRO_AIMING = 7 } private object QuickMenuTab { @@ -247,6 +249,14 @@ fun QuickMenu( accentColor = PluviaTheme.colors.accentPurple, ) ) + add( + QuickMenuItem( + id = QuickMenuAction.GYRO_AIMING, + icon = Icons.Default.GpsFixed, + labelResId = R.string.gyro_aiming, + accentColor = PluviaTheme.colors.accentCyan, + ) + ) if (hasPhysicalController) { add( QuickMenuItem( diff --git a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt index 2ef1543fe7..b8c090e83a 100644 --- a/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt +++ b/app/src/main/java/app/gamenative/ui/component/dialog/ControllerTab.kt @@ -1,7 +1,11 @@ package app.gamenative.ui.component.dialog import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlin.math.abs import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -24,6 +28,8 @@ import com.alorma.compose.settings.ui.SettingsSwitch import com.alorma.compose.settings.ui.SettingsMenuLink import com.winlator.container.Container +private val GYRO_SENSITIVITY_VALUES = listOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f) + @Composable fun ControllerTabContent(state: ContainerConfigState, default: Boolean) { val config = state.config.value @@ -98,6 +104,16 @@ fun ControllerTabContent(state: ContainerConfigState, default: Boolean) { state = config.shooterMode, onCheckedChange = { state.config.value = config.copy(shooterMode = it) }, ) + SettingsListDropdown( + colors = settingsTileColors(), + title = { Text(text = stringResource(R.string.gyro_sensitivity)) }, + subtitle = { Text(text = stringResource(R.string.gyro_sensitivity_description)) }, + value = (GYRO_SENSITIVITY_VALUES.withIndex().minByOrNull { abs(it.value - config.gyroSensitivity) }?.index ?: 3).coerceIn(0, GYRO_SENSITIVITY_VALUES.lastIndex), + items = GYRO_SENSITIVITY_VALUES.map { "${(it * 100).toInt()}%" }, + onItemSelected = { index -> + state.config.value = config.copy(gyroSensitivity = GYRO_SENSITIVITY_VALUES[index]) + }, + ) SettingsListDropdown( colors = settingsTileColors(), title = { Text(text = stringResource(R.string.external_display_input)) }, diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index 4a0ea44887..ed7b25371f 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -938,6 +938,19 @@ fun XServerScreen( false } + QuickMenuAction.GYRO_AIMING -> { + val currentlyEnabled = container.getExtra("gyroAiming", "false") == "true" + val newEnabled = !currentlyEnabled + container.putExtra("gyroAiming", if (newEnabled) "true" else "false") + container.saveData() + PluviaApp.touchpadView?.setGyroAimingEnabled(newEnabled) + PostHog.capture( + event = "gyro_aiming_toggled", + properties = mapOf("enabled" to newEnabled), + ) + true + } + QuickMenuAction.EXIT_GAME -> { PostHog.capture( event = "game_closed", @@ -1367,6 +1380,8 @@ fun XServerScreen( PluviaApp.touchpadView = TouchpadView(context, getxServer(), PrefManager.getBoolean("capture_pointer_on_external_mouse", true)) frameLayout.addView(PluviaApp.touchpadView) PluviaApp.touchpadView?.setMoveCursorToTouchpoint(PrefManager.getBoolean("move_cursor_to_touchpoint", false)) + PluviaApp.touchpadView?.setGyroSensitivity(container.getExtra("gyroSensitivity", "1").toFloatOrNull()?.coerceIn(0.25f, 2f) ?: 1f) + PluviaApp.touchpadView?.setGyroAimingEnabled(container.getExtra("gyroAiming", "false") == "true") // Add invisible IME receiver to capture system keyboard input when keyboard is on external display val imeDisplayContext = context.display?.let { display -> diff --git a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt index 2c3e7b0ebe..e7241ec2d6 100644 --- a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt +++ b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt @@ -317,6 +317,7 @@ object ContainerUtils { sharpnessEffect = container.getExtra("sharpnessEffect", "None"), sharpnessLevel = container.getExtra("sharpnessLevel", "100").toIntOrNull() ?: 100, sharpnessDenoise = container.getExtra("sharpnessDenoise", "100").toIntOrNull() ?: 100, + gyroSensitivity = container.getExtra("gyroSensitivity", "1").toFloatOrNull()?.coerceIn(0.25f, 2f) ?: 1f, ) } @@ -483,6 +484,7 @@ object ContainerUtils { container.putExtra("sharpnessEffect", containerData.sharpnessEffect) container.putExtra("sharpnessLevel", containerData.sharpnessLevel.toString()) container.putExtra("sharpnessDenoise", containerData.sharpnessDenoise.toString()) + container.putExtra("gyroSensitivity", containerData.gyroSensitivity.coerceIn(0.25f, 2f).toString()) try { container.language = containerData.language } catch (e: Exception) { diff --git a/app/src/main/java/com/winlator/container/ContainerData.kt b/app/src/main/java/com/winlator/container/ContainerData.kt index 5d0da2b8e7..f2b6c6667c 100644 --- a/app/src/main/java/com/winlator/container/ContainerData.kt +++ b/app/src/main/java/com/winlator/container/ContainerData.kt @@ -94,6 +94,7 @@ data class ContainerData( val sharpnessEffect: String = "None", val sharpnessLevel: Int = 100, val sharpnessDenoise: Int = 100, + val gyroSensitivity: Float = 1f, ) { companion object { val Saver = mapSaver( @@ -157,6 +158,7 @@ data class ContainerData( "sharpnessEffect" to state.sharpnessEffect, "sharpnessLevel" to state.sharpnessLevel, "sharpnessDenoise" to state.sharpnessDenoise, + "gyroSensitivity" to state.gyroSensitivity, ) }, restore = { savedMap -> @@ -219,6 +221,7 @@ data class ContainerData( sharpnessEffect = (savedMap["sharpnessEffect"] as? String) ?: "None", sharpnessLevel = (savedMap["sharpnessLevel"] as? Int) ?: 100, sharpnessDenoise = (savedMap["sharpnessDenoise"] as? Int) ?: 100, + gyroSensitivity = (savedMap["gyroSensitivity"] as? Float) ?: (savedMap["gyroSensitivity"] as? Double)?.toFloat() ?: 1f, ) }, ) diff --git a/app/src/main/java/com/winlator/widget/GyroAimingHelper.java b/app/src/main/java/com/winlator/widget/GyroAimingHelper.java new file mode 100644 index 0000000000..e622fd90ee --- /dev/null +++ b/app/src/main/java/com/winlator/widget/GyroAimingHelper.java @@ -0,0 +1,179 @@ +package com.winlator.widget; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.view.Display; +import android.view.Surface; + +import timber.log.Timber; + +/** + * Listens to the device gyroscope and converts angular velocity to relative mouse deltas + * for aiming. Only active when started; use from the main thread. + */ +public class GyroAimingHelper implements SensorEventListener { + public interface Listener { + void onMouseDelta(int dx, int dy); + } + + private static final float DEFAULT_SENSITIVITY = 400f; + private static final float MAX_DELTA_PER_FRAME = 120f; + private static final int SENSOR_DELAY_US = 5_000; // 200 Hz + + private final Context context; + private final Listener listener; + private final Handler mainHandler; + private float sensitivity; + + private SensorManager sensorManager; + private DisplayManager displayManager; + private Sensor gyro; + private boolean running; + private long lastTimestampNs = 0; + private boolean hasLastTimestamp; + /** Sub-pixel accumulation so small movements aren't lost when casting to int */ + private float accumDx = 0f; + private float accumDy = 0f; + + public GyroAimingHelper(Context context, Listener listener) { + this(context, listener, DEFAULT_SENSITIVITY); + } + + public GyroAimingHelper(Context context, Listener listener, float sensitivity) { + this.context = context.getApplicationContext(); + this.listener = listener; + this.sensitivity = sensitivity > 0 ? sensitivity : DEFAULT_SENSITIVITY; + this.mainHandler = new Handler(Looper.getMainLooper()); + } + + public void start() { + if (running) return; + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + if (sensorManager == null) { + Timber.w("GyroAiming: SensorManager is null"); + return; + } + gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); + if (gyro == null) { + Timber.w("GyroAiming: no TYPE_GYROSCOPE sensor on this device"); + return; + } + hasLastTimestamp = false; + accumDx = 0f; + accumDy = 0f; + boolean registered = sensorManager.registerListener(this, gyro, SENSOR_DELAY_US); + if (!registered) { + Timber.e("GyroAiming: failed to start, registerListener returned false (sensor=%s)", gyro.getName()); + return; + } + running = true; + Timber.d("GyroAiming: started (sensor=%s)", gyro.getName()); + } + + public void stop() { + if (!running) return; + running = false; + mainHandler.removeCallbacksAndMessages(null); + if (sensorManager != null && gyro != null) { + sensorManager.unregisterListener(this, gyro); + } + sensorManager = null; + displayManager = null; + gyro = null; + Timber.d("GyroAiming: stopped"); + } + + public boolean isRunning() { + return running; + } + + public void setSensitivity(float sensitivity) { + this.sensitivity = sensitivity > 0 ? sensitivity : DEFAULT_SENSITIVITY; + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (!running) return; + if (event.sensor.getType() != Sensor.TYPE_GYROSCOPE || listener == null) return; + + long t = event.timestamp; + if (!hasLastTimestamp) { + lastTimestampNs = t; + hasLastTimestamp = true; + return; + } + float dt = (t - lastTimestampNs) * 1e-9f; + lastTimestampNs = t; + if (dt <= 0 || dt > 0.5f) return; // skip invalid or huge gaps + + float deviceRadX = event.values[0]; + float deviceRadY = event.values[1]; + + float radX; + float radY; + // Offset by +90deg to match aiming orientation. + int effectiveRotation = (getDisplayRotation() + 1) & 0x3; + switch (effectiveRotation) { + case Surface.ROTATION_90: + radX = deviceRadY; + radY = -deviceRadX; + break; + case Surface.ROTATION_180: + radX = -deviceRadX; + radY = -deviceRadY; + break; + case Surface.ROTATION_270: + radX = -deviceRadY; + radY = deviceRadX; + break; + case Surface.ROTATION_0: + default: + radX = deviceRadX; + radY = deviceRadY; + break; + } + + float dx = -radX * dt * sensitivity; + float dy = radY * dt * sensitivity; + + // Clamp per-frame delta for stability + dx = clamp(dx, -MAX_DELTA_PER_FRAME, MAX_DELTA_PER_FRAME); + dy = clamp(dy, -MAX_DELTA_PER_FRAME, MAX_DELTA_PER_FRAME); + + accumDx += dx; + accumDy += dy; + int ix = (int) accumDx; + int iy = (int) accumDy; + if (ix != 0 || iy != 0) { + accumDx -= ix; + accumDy -= iy; + final int fix = ix; + final int fiy = iy; + mainHandler.post(() -> { + if (!running) return; + listener.onMouseDelta(fix, fiy); + }); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} + + private int getDisplayRotation() { + if (displayManager == null) return Surface.ROTATION_0; + Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + if (display == null) return Surface.ROTATION_0; + return display.getRotation(); + } + + private static float clamp(float v, float lo, float hi) { + return v < lo ? lo : (v > hi ? hi : v); + } +} diff --git a/app/src/main/java/com/winlator/widget/TouchpadView.java b/app/src/main/java/com/winlator/widget/TouchpadView.java index e860a09f11..9d2c604a7f 100644 --- a/app/src/main/java/com/winlator/widget/TouchpadView.java +++ b/app/src/main/java/com/winlator/widget/TouchpadView.java @@ -27,6 +27,8 @@ import com.winlator.xserver.XKeycode; import com.winlator.xserver.XServer; +import timber.log.Timber; + public class TouchpadView extends View implements View.OnCapturedPointerListener { private static final byte MAX_FINGERS = 4; private static final short MAX_TWO_FINGERS_SCROLL_DISTANCE = 350; @@ -111,6 +113,12 @@ public class TouchpadView extends View implements View.OnCapturedPointerListener // Accumulated deltas for deciding which two-finger gesture to lock into private float accumulatedPinchDelta, accumulatedPanDelta; + // Gyro aiming: device rotation drives relative mouse movement + private static final float GYRO_BASE_SENSITIVITY = 400f; + private GyroAimingHelper gyroAimingHelper; + private boolean gyroAimingEnabled; + private float gyroSensitivity = 1f; + public TouchpadView(Context context, XServer xServer, boolean capturePointerOnExternalMouse) { super(context); this.capturePointerOnExternalMouse = capturePointerOnExternalMouse; @@ -143,6 +151,12 @@ public TouchpadView(Context context, XServer xServer, boolean capturePointerOnEx } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setGyroAimingEnabled(false); + } + @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); @@ -1301,4 +1315,42 @@ public TouchGestureConfig getGestureConfig() { public void setTouchscreenMouseDisabled(boolean disabled) { this.touchscreenMouseDisabled = disabled; } + + public void setGyroAimingEnabled(boolean enabled) { + if (gyroAimingEnabled == enabled) return; + gyroAimingEnabled = enabled; + Timber.d("GyroAiming: setGyroAimingEnabled(%s)", enabled); + if (enabled) { + if (gyroAimingHelper == null) { + float sens = GYRO_BASE_SENSITIVITY * gyroSensitivity; + gyroAimingHelper = new GyroAimingHelper(getContext(), (dx, dy) -> { + if (!isEnabled()) return; + if (!gyroAimingEnabled) return; + if (xServer.isRelativeMouseMovement()) { + xServer.getWinHandler().mouseEvent(MouseEventFlags.MOVE, dx, dy, 0); + } else { + xServer.injectPointerMoveDelta(dx, dy); + } + }, sens); + } else { + gyroAimingHelper.setSensitivity(GYRO_BASE_SENSITIVITY * gyroSensitivity); + } + gyroAimingHelper.start(); + } else { + if (gyroAimingHelper != null) { + gyroAimingHelper.stop(); + } + } + } + + public void setGyroSensitivity(float sensitivity) { + gyroSensitivity = sensitivity > 0 ? sensitivity : 1f; + if (gyroAimingHelper != null) { + gyroAimingHelper.setSensitivity(GYRO_BASE_SENSITIVITY * gyroSensitivity); + } + } + + public boolean isGyroAimingEnabled() { + return gyroAimingEnabled; + } } diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index af305dd3fd..4e4e93eba3 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -260,6 +260,7 @@ Touchpad-hjælp Skjul on-screen-kontroller med controller Skjul automatisk on-screen-kontroller når en fysisk controller er tilsluttet + Gyro-mus-sigte Vælg binding @@ -530,6 +531,8 @@ Til visuelle romaner og RTS-spil Shooter-tilstand Erstat stick-elementer med dynamiske joysticks + Gyro-følsomhed + Multiplikator for gyro-mus-sigte (når aktiveret in-game) Aktiver mus Aktiver sticks diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index af5057ffbe..b57bc7c284 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -325,6 +325,7 @@ Touchpad-Hilfe On-Screen-Controller bei Gamepad ausblenden On-Screen-Steuerung automatisch ausblenden, wenn ein physischer Controller verbunden ist + Gyro-Mauszielen Belegung auswählen Belege: %1$s @@ -680,6 +681,8 @@ Für Visual Novels und RTS-Spiele Shooter-Modus Stick-Elemente durch dynamische Joysticks ersetzen + Gyro-Empfindlichkeit + Multiplikator für Gyro-Mauszielen (wenn im Spiel aktiviert) Maus aktivieren Sticks aktivieren diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d8ee4df584..32893dfbac 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -347,6 +347,7 @@ Ayuda del touchpad Ocultar controles táctiles al usar mando Oculta automáticamente los controles en pantalla cuando se conecta un mando físico. + Apuntado con gyro (ratón) Seleccionar asignación @@ -755,6 +756,8 @@ Para novelas visuales y juegos de estrategia en tiempo real. Modo tirador Reemplaza los sticks por joysticks dinámicos. + Sensibilidad gyro + Multiplicador para apuntado con gyro/ratón (cuando está activado en el juego) Activar ratón Activar sticks diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1b63a8bc9d..6dcb85d1bb 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -332,6 +332,7 @@ Aide du pavé tactile Masquer les contrôles à l\'écran avec manette Masquer automatiquement les contrôles à l\'écran lorsqu\'un contrôleur physique est connecté + Visée souris gyro Sélectionner l\'affectation @@ -706,6 +707,8 @@ Pour les visual novels et jeux de stratégie en temps réel Mode tireur Remplacer les sticks par des joysticks dynamiques + Sensibilité gyro + Multiplicateur pour la visée souris gyro (quand activé en jeu) Activer la souris Activer les sticks diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9d6be35376..e0d23b583a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -336,6 +336,7 @@ Aiuto Touchpad Nascondi controlli a schermo con controller Nascondi automaticamente i controlli a schermo quando è connesso un controller fisico + Mira con gyro (mouse) Seleziona Associazione @@ -710,6 +711,8 @@ Per visual novel e giochi RTS Modalità sparatutto Sostituisci gli stick con joystick dinamici + Sensibilità gyro + Moltiplicatore per mira con gyro/mouse (quando attivato in-game) Attiva mouse Attiva stick diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f9eff06fc9..fa3ad5de48 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -345,6 +345,7 @@ 터치패드 도움말 컨트롤러 연결 시 화면 컨트롤 숨기기 물리 컨트롤러가 연결되면 화면 컨트롤을 자동으로 숨깁니다 + 자이로 마우스 조준 바인딩 선택 @@ -720,6 +721,8 @@ 비주얼 노벨 및 RTS 게임용 슈터 모드 스틱 요소를 동적 조이스틱으로 교체 + 자이로 감도 + 자이로 마우스 조준 배율(인게임에서 활성화 시) 마우스 활성화 스틱 활성화 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d3c4748801..cb15c772cd 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -344,6 +344,7 @@ Pomoc touchpada Ukryj kontroler ekranowy przy podłączonym kontrolerze Automatycznie ukrywa kontroler ekranowy, gdy podłączony jest kontroler fizyczny + Celowanie gyro (mysz) Wybierz przypisanie @@ -719,6 +720,8 @@ Dla powieści wizualnych i gier RTS Tryb strzelanki Zamień drążki na dynamiczne joysticki + Czułość żyroskopu + Mnożnik celowania gyro myszą (gdy włączone w grze) Włącz mysz Włącz drążki diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 80545536e2..bb4db94fad 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -260,6 +260,7 @@ Ajuda Touchpad Ocultar controles on-screen com controle Ocultar automaticamente controles on-screen quando um controle físico estiver conectado + Mira com gyro (mouse) Selecionar Binding @@ -530,6 +531,8 @@ Para visual novels e jogos de estratégia em tempo real Modo tiro Substituir sticks por joysticks dinâmicos + Sensibilidade do gyro + Multiplicador para mira com gyro/mouse (quando ativado no jogo) Ativar mouse Ativar sticks diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index c484ef78e5..2b06027c66 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -334,6 +334,7 @@ Ajutor touchpad Ascunde controalele pe ecran când există controller Ascunde automat controalele pe ecran când un controller fizic este conectat + Țintire mouse gyro Selectează acțiunea @@ -711,6 +712,8 @@ Pentru visual novels și jocuri RTS Mod shooter Înlocuiește stick-urile cu joystick-uri dinamice + Sensibilitate gyro + Multiplicator pentru țintire mouse gyro (când este activat în joc) Activează mouse-ul Activează stick-urile diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c1ed94e4b2..021cfe6c4e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1019,6 +1019,8 @@ https://gamenative.app Настройки динамических джойстиков Режим стрельбы Автоматически заменять элементы джойстика на динамические джойстики + Чувствительность гироскопа + Множитель прицеливания гироскопом/мышью (при включении в игре) Ярлыки Шифты Показать FPS @@ -1179,6 +1181,7 @@ https://gamenative.app показать подсказку Общее время игры: %s часов Справка сенсорной панели + Прицеливание гироскопом (мышь) Режим сенсорного экрана Для визуальных романов и игр RTS Введите 5-значный код diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5b0890c4d8..92d8dba8c7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -327,6 +327,7 @@ Довідка сенсорної панелі Приховувати екранні елементи з контролером Автоматично приховувати екранні елементи керування, коли під\'єднано фізичний контролер + Прицілювання гіроскопом (миша) Вибрати призначення @@ -701,6 +702,8 @@ Для візуальних романів та ігор RTS Режим шутера Замінити джойстики на динамічні + Чутливість гіроскопа + Множник прицілювання гіроскопом (коли увімкнено в грі) Увімкнути мишу Увімкнути джойстики diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 09b469cbbc..3780dda2c9 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -330,6 +330,7 @@ 触摸板支持 使用控制器时隐藏屏幕控制器 当实体控制器连接时自动隐藏屏幕控制器 + 陀螺仪鼠标瞄准 选择绑定 @@ -701,6 +702,8 @@ 适用于视觉小说和即时战略游戏 射击模式 用动态摇杆替换摇杆元素 + 陀螺仪灵敏度 + 陀螺仪鼠标瞄准倍率(在游戏中启用时) 启用鼠标 启用摇杆 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8d5d6fc539..afcaf01b94 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -330,6 +330,7 @@ 觸摸板支援 使用控制器時隱藏螢幕控制器 當實體控制器連接時自動隱藏螢幕控制器 + 陀螺儀滑鼠瞄準 選擇綁定 @@ -703,6 +704,8 @@ 適用於視覺小說與即時戰略遊戲 射擊模式 以動態搖桿取代搖桿元素 + 陀螺儀靈敏度 + 陀螺儀滑鼠瞄準倍率(在遊戲中啟用時) 啟用滑鼠 啟用搖桿 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index feb1d794b1..85bc09f45c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -297,6 +297,7 @@ Clock time CPU temperature GPU temperature + Gyro mouse aiming Touchpad Help Hide on-screen controls with controller Automatically hide on-screen controls when a physical controller is connected @@ -710,6 +711,8 @@ For visual novels and RTS games Shooter Mode Auto-replace stick elements with dynamic joysticks + Gyro sensitivity + Multiplier for gyro mouse aiming (when enabled in-game) Enable Mouse Enable Sticks