215 lines
8.8 KiB
Java
215 lines
8.8 KiB
Java
package com.phenomrom.iwanttoheal;
|
|
|
|
import android.os.Bundle;
|
|
import android.os.SystemClock;
|
|
import android.view.InputDevice;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.WindowManager;
|
|
|
|
import androidx.activity.OnBackPressedCallback;
|
|
import androidx.core.view.WindowCompat;
|
|
import androidx.core.view.WindowInsetsCompat;
|
|
import androidx.core.view.WindowInsetsControllerCompat;
|
|
|
|
import com.getcapacitor.BridgeActivity;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
/** Routes every Thor controller event into one JavaScript input service without WebView focus. */
|
|
public abstract class ControllerBridgeActivity extends BridgeActivity {
|
|
private static final float AXIS_DEAD_ZONE = 0.45f;
|
|
private static final long REPEAT_THROTTLE_MS = 55L;
|
|
private final long[] lastDpadDispatchAt = new long[16];
|
|
private final Set<String> activeMotionTokens = new HashSet<>();
|
|
private final Map<String, Long> lastMotionDispatchAt = new HashMap<>();
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
|
@Override
|
|
public void handleOnBackPressed() {
|
|
dispatchNativeControllerToken("Button1", false);
|
|
}
|
|
});
|
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
WindowManager.LayoutParams attributes = getWindow().getAttributes();
|
|
attributes.preferredRefreshRate = 60.0f;
|
|
getWindow().setAttributes(attributes);
|
|
if (bridge != null && bridge.getWebView() != null) {
|
|
bridge.getWebView().setOverScrollMode(View.OVER_SCROLL_NEVER);
|
|
}
|
|
enterImmersiveMode();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
clearHeldControllerState();
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
enterImmersiveMode();
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
String token = controllerToken(event.getKeyCode());
|
|
if (token == null || bridge == null) return super.dispatchKeyEvent(event);
|
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
boolean repeat = event.getRepeatCount() > 0;
|
|
if (isDpadToken(token) && shouldThrottleDpad(token)) return true;
|
|
dispatchNativeControllerToken(token, repeat);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
|
if (
|
|
bridge == null
|
|
|| event.getActionMasked() != MotionEvent.ACTION_MOVE
|
|
|| !isControllerMotionEvent(event)
|
|
) {
|
|
return super.dispatchGenericMotionEvent(event);
|
|
}
|
|
|
|
float leftStickX = event.getAxisValue(MotionEvent.AXIS_X);
|
|
float leftStickY = event.getAxisValue(MotionEvent.AXIS_Y);
|
|
float rightStickX = controllerAxisValue(event, MotionEvent.AXIS_Z, MotionEvent.AXIS_RX);
|
|
float rightStickY = controllerAxisValue(event, MotionEvent.AXIS_RZ, MotionEvent.AXIS_RY);
|
|
dispatchNativeControllerMotion(leftStickX, leftStickY, rightStickX, rightStickY);
|
|
|
|
Set<String> currentTokens = new HashSet<>();
|
|
addAxisTokens(currentTokens, event.getAxisValue(MotionEvent.AXIS_HAT_X), "Button14", "Button15");
|
|
addAxisTokens(currentTokens, event.getAxisValue(MotionEvent.AXIS_HAT_Y), "Button12", "Button13");
|
|
addAxisTokens(currentTokens, leftStickX, "Axis0-", "Axis0+");
|
|
addAxisTokens(currentTokens, leftStickY, "Axis1-", "Axis1+");
|
|
|
|
long now = SystemClock.uptimeMillis();
|
|
for (String token : currentTokens) {
|
|
boolean repeat = activeMotionTokens.contains(token);
|
|
long lastDispatch = lastMotionDispatchAt.containsKey(token)
|
|
? lastMotionDispatchAt.get(token)
|
|
: 0L;
|
|
if (!repeat || now - lastDispatch >= REPEAT_THROTTLE_MS) {
|
|
dispatchNativeControllerToken(token, repeat);
|
|
lastMotionDispatchAt.put(token, now);
|
|
}
|
|
}
|
|
activeMotionTokens.clear();
|
|
activeMotionTokens.addAll(currentTokens);
|
|
return true;
|
|
}
|
|
|
|
private void enterImmersiveMode() {
|
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
|
WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(
|
|
getWindow(),
|
|
getWindow().getDecorView()
|
|
);
|
|
controller.hide(WindowInsetsCompat.Type.systemBars());
|
|
controller.setSystemBarsBehavior(
|
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
);
|
|
}
|
|
|
|
private boolean isControllerMotionEvent(MotionEvent event) {
|
|
int source = event.getSource();
|
|
return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|
|
|| (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
|
|
|| (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
|
|
}
|
|
|
|
private void addAxisTokens(Set<String> tokens, float value, String negative, String positive) {
|
|
if (value <= -AXIS_DEAD_ZONE) tokens.add(negative);
|
|
if (value >= AXIS_DEAD_ZONE) tokens.add(positive);
|
|
}
|
|
|
|
private float controllerAxisValue(MotionEvent event, int primaryAxis, int fallbackAxis) {
|
|
InputDevice device = event.getDevice();
|
|
if (device != null && device.getMotionRange(primaryAxis) != null) {
|
|
return event.getAxisValue(primaryAxis);
|
|
}
|
|
return event.getAxisValue(fallbackAxis);
|
|
}
|
|
|
|
private void dispatchNativeControllerToken(String token, boolean repeat) {
|
|
if (bridge == null || bridge.getWebView() == null) return;
|
|
String script =
|
|
"window.dispatchEvent(new CustomEvent('iwt-native-controller',"
|
|
+ "{detail:{token:'" + token + "',repeat:" + repeat + "}}));";
|
|
bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript(script, null));
|
|
}
|
|
|
|
private void dispatchNativeControllerMotion(float moveX, float moveY, float lookX, float lookY) {
|
|
if (bridge == null || bridge.getWebView() == null) return;
|
|
String script =
|
|
"window.dispatchEvent(new CustomEvent('iwt-native-controller-motion',"
|
|
+ "{detail:{moveX:" + moveX + ",moveY:" + moveY
|
|
+ ",lookX:" + lookX + ",lookY:" + lookY + "}}));";
|
|
bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript(script, null));
|
|
}
|
|
|
|
private void clearHeldControllerState() {
|
|
activeMotionTokens.clear();
|
|
lastMotionDispatchAt.clear();
|
|
dispatchNativeControllerMotion(0.0f, 0.0f, 0.0f, 0.0f);
|
|
if (bridge == null || bridge.getWebView() == null) return;
|
|
bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript(
|
|
"window.dispatchEvent(new Event('iwt-native-controller-reset'));",
|
|
null
|
|
));
|
|
}
|
|
|
|
private boolean shouldThrottleDpad(String token) {
|
|
int index = Integer.parseInt(token.substring("Button".length()));
|
|
long now = SystemClock.uptimeMillis();
|
|
if (now - lastDpadDispatchAt[index] < REPEAT_THROTTLE_MS) return true;
|
|
lastDpadDispatchAt[index] = now;
|
|
return false;
|
|
}
|
|
|
|
private boolean isDpadToken(String token) {
|
|
return token.equals("Button12")
|
|
|| token.equals("Button13")
|
|
|| token.equals("Button14")
|
|
|| token.equals("Button15");
|
|
}
|
|
|
|
private String controllerToken(int keyCode) {
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_BUTTON_A:
|
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
|
case KeyEvent.KEYCODE_ENTER:
|
|
case KeyEvent.KEYCODE_NUMPAD_ENTER:
|
|
return "Button0";
|
|
case KeyEvent.KEYCODE_BUTTON_B:
|
|
case KeyEvent.KEYCODE_ESCAPE:
|
|
return "Button1";
|
|
case KeyEvent.KEYCODE_BUTTON_X: return "Button2";
|
|
case KeyEvent.KEYCODE_BUTTON_Y: return "Button3";
|
|
case KeyEvent.KEYCODE_BUTTON_L1: return "Button4";
|
|
case KeyEvent.KEYCODE_BUTTON_R1: return "Button5";
|
|
case KeyEvent.KEYCODE_BUTTON_L2: return "Button6";
|
|
case KeyEvent.KEYCODE_BUTTON_R2: return "Button7";
|
|
case KeyEvent.KEYCODE_BUTTON_SELECT: return "Button8";
|
|
case KeyEvent.KEYCODE_BUTTON_START: return "Button9";
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBL: return "Button10";
|
|
case KeyEvent.KEYCODE_BUTTON_THUMBR: return "Button11";
|
|
case KeyEvent.KEYCODE_DPAD_UP: return "Button12";
|
|
case KeyEvent.KEYCODE_DPAD_DOWN: return "Button13";
|
|
case KeyEvent.KEYCODE_DPAD_LEFT: return "Button14";
|
|
case KeyEvent.KEYCODE_DPAD_RIGHT: return "Button15";
|
|
default: return null;
|
|
}
|
|
}
|
|
}
|