Android: Handle mouse events separately from touch events

Use the new modern events like HOVER.
This commit is contained in:
Henrik Rydgård
2025-01-23 19:02:08 +01:00
parent beb3eafa1f
commit 8674488859
10 changed files with 233 additions and 46 deletions
+1 -1
View File
@@ -958,7 +958,7 @@ std::string GridLayoutList::DescribeText() const {
}
TabHolder::TabHolder(Orientation orientation, float stripSize, LayoutParams *layoutParams)
: LinearLayout(Opposite(orientation), layoutParams), stripSize_(stripSize) {
: LinearLayout(Opposite(orientation), layoutParams) {
SetSpacing(0.0f);
if (orientation == ORIENT_HORIZONTAL) {
tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-1
View File
@@ -322,7 +322,6 @@ private:
ScrollView *tabScroll_ = nullptr;
AnchorLayout *contents_ = nullptr;
float stripSize_;
int currentTab_ = 0;
std::vector<View *> tabs_;
std::vector<AnchorTranslateTween *> tabTweens_;
+5 -7
View File
@@ -1268,6 +1268,11 @@ bool TouchTestScreen::key(const KeyInput &key) {
}
void TouchTestScreen::axis(const AxisInput &axis) {
if (axis.deviceId == DEVICE_ID_MOUSE && (axis.axisId == JOYSTICK_AXIS_MOUSE_REL_X || axis.axisId == JOYSTICK_AXIS_MOUSE_REL_Y)) {
// These spam a lot, don't log for now.
return;
}
char buf[512];
snprintf(buf, sizeof(buf), "Axis: %s (%d) (value %1.3f) Device ID: %d",
KeyMap::GetAxisName(axis.axisId).c_str(), axis.axisId, axis.value, axis.deviceId);
@@ -1311,12 +1316,6 @@ void TouchTestScreen::DrawForeground(UIContext &dc) {
truncate_cpy(extra_debug, Android_GetInputDeviceDebugString().c_str());
#endif
// Hm, why don't we print all the info on Android?
#if PPSSPP_PLATFORM(ANDROID)
snprintf(buffer, sizeof(buffer),
"display_res: %dx%d\n",
(int)System_GetPropertyInt(SYSPROP_DISPLAY_XRES), (int)System_GetPropertyInt(SYSPROP_DISPLAY_YRES));
#else
snprintf(buffer, sizeof(buffer),
"display_res: %dx%d\n"
"dp_res: %dx%d pixel_res: %dx%d\n"
@@ -1329,7 +1328,6 @@ void TouchTestScreen::DrawForeground(UIContext &dc) {
g_display.dpi_scale_real,
delta * 1000.0, 1.0 / delta,
extra_debug);
#endif
// On Android, also add joystick debug data.
dc.DrawTextShadow(buffer, bounds.centerX(), bounds.y + 20.0f, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
+64
View File
@@ -1242,6 +1242,70 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
env->ReleaseFloatArrayElements(values, valueBuffer, JNI_ABORT); // ABORT just means we don't want changes copied back!
}
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouse(
JNIEnv *env, jclass, jfloat x, jfloat y, int button, int action) {
if (!renderer_inited)
return false;
TouchInput input{};
static float last_x = 0.0f;
static float last_y = 0.0f;
if (x == -1.0f) {
x = last_x;
} else {
last_x = x;
}
if (y == -1.0f) {
y = last_y;
} else {
last_y = y;
}
x *= g_display.dpi_scale;
y *= g_display.dpi_scale;
if (button == 0) {
// It's a pure mouse move.
input.flags = TOUCH_MOUSE | TOUCH_MOVE;
input.x = x;
input.y = y;
input.id = 0;
} else {
input.buttons = button;
input.x = x;
input.y = y;
switch (action) {
case 1:
input.flags = TOUCH_MOUSE | TOUCH_DOWN;
break;
case 2:
input.flags = TOUCH_MOUSE | TOUCH_UP;
break;
}
input.id = 0;
}
INFO_LOG(Log::System, "New-style mouse event: %f %f %d %d -> x: %f y: %f buttons: %d flags: %04x", x, y, button, action, input.x, input.y, input.buttons, input.flags);
NativeTouch(input);
// Also send mouse button key events, for binding.
if (button) {
KeyInput input{};
input.deviceId = DEVICE_ID_MOUSE;
switch (button) {
case 1: input.keyCode = NKCODE_EXT_MOUSEBUTTON_1; break;
case 2: input.keyCode = NKCODE_EXT_MOUSEBUTTON_2; break;
case 3: input.keyCode = NKCODE_EXT_MOUSEBUTTON_3; break;
default: WARN_LOG(Log::System, "Unexpected mouse button %d", button);
}
input.flags = action == 1 ? KEY_DOWN : KEY_UP;
if (input.keyCode != 0) {
NativeKey(input);
}
}
return true;
}
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
JNIEnv *env, jclass, jfloat x, jfloat y) {
if (!renderer_inited)
@@ -163,6 +163,7 @@ public class InputDeviceState {
Log.i(TAG, "Not a joystick event: source = " + event.getSource());
return false;
}
Log.i(TAG, "onjoystick");
int count = 0;
for (int i = 0; i < mAxes.length; i++) {
int axisId = mAxes[i];
+1 -1
View File
@@ -68,7 +68,7 @@ public class MogaHack {
// Convert implicit intent to explicit intent, see http://stackoverflow.com/a/26318757
Intent intent = new Intent(IControllerService.class.getName());
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(intent, 0);
if (resolveInfos == null || resolveInfos.size() != 1) {
if (resolveInfos.size() != 1) {
// What? this doesn't do anything.
// Log.e("MogaHack", "Somebody is trying to intercept our intent. Disabling MOGA controller for security.");
}
@@ -97,8 +97,6 @@ public abstract class NativeActivity extends Activity {
private Vibrator vibrator;
private boolean isXperiaPlay;
// This is to avoid losing the game/menu state etc when we are just
// switched-away from or rotated etc.
private boolean shuttingDown;
@@ -136,6 +134,8 @@ public abstract class NativeActivity extends Activity {
public static final int REQUEST_CODE_CAMERA_PERMISSION = 3;
public static final int REQUEST_CODE_MICROPHONE_PERMISSION = 4;
public static boolean useModernMouseEvents = false;
// Functions for the app activity to override to change behaviour.
public native void registerCallbacks();
@@ -275,7 +275,7 @@ public abstract class NativeActivity extends Activity {
for (String var : varNames) {
Log.i(TAG, "getSdCardPaths: Checking env " + var);
String secStore = System.getenv("SECONDARY_STORAGE");
if (secStore != null && secStore.length() > 0) {
if (secStore != null && !secStore.isEmpty()) {
list = new ArrayList<String>();
list.add(secStore);
break;
@@ -399,8 +399,6 @@ public abstract class NativeActivity extends Activity {
// All other device types are treated the same.
}
isXperiaPlay = IsXperiaPlay();
String extStorageState = Environment.getExternalStorageState();
String extStorageDir = Environment.getExternalStorageDirectory().getAbsolutePath();
File externalFiles = this.getExternalFilesDir(null);
@@ -573,10 +571,8 @@ public abstract class NativeActivity extends Activity {
// Need API 11 to check for existence of a vibrator? Zany.
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void checkForVibrator() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (!vibrator.hasVibrator()) {
vibrator = null;
}
if (!vibrator.hasVibrator()) {
vibrator = null;
}
}
@@ -780,7 +776,7 @@ public abstract class NativeActivity extends Activity {
do {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
} catch (InterruptedException ignored) {
}
tries--;
} while (nativeRenderer.isRenderingFrame() && tries > 0);
@@ -973,7 +969,7 @@ public abstract class NativeActivity extends Activity {
for (InputDeviceState input : inputPlayers) {
buffer += input.getDebugString();
}
if (buffer.length() == 0) {
if (buffer.isEmpty()) {
buffer = "(no devices)";
}
return buffer;
@@ -982,15 +978,28 @@ public abstract class NativeActivity extends Activity {
}
}
public boolean IsXperiaPlay() {
return android.os.Build.MODEL.equals("R800a") || android.os.Build.MODEL.equals("R800i") || android.os.Build.MODEL.equals("R800x") || android.os.Build.MODEL.equals("R800at") || android.os.Build.MODEL.equals("SO-01D") || android.os.Build.MODEL.equals("zeus");
}
// We grab the keys before onKeyDown/... even see them. This is also better because it lets us
// distinguish devices.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && !isXperiaPlay) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
Log.i(TAG, "key event" + event.getSource());
if (NativeSurfaceView.isFromSource(event, InputDevice.SOURCE_MOUSE)) {
Log.i(TAG, "Forwarding key event from mouse");
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && !useModernMouseEvents) {
// Probably a right click
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
NativeApp.mouse(-1, -1, 2, 1);
break;
case KeyEvent.ACTION_UP:
NativeApp.mouse(-1, -1, 2, 2);
break;
}
}
return true;
}
InputDeviceState state = getInputDeviceState(event);
if (state == null) {
return super.dispatchKeyEvent(event);
@@ -1022,12 +1031,14 @@ public abstract class NativeActivity extends Activity {
if (!passThrough) {
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
Log.i(TAG, "KeyEvent Down");
if (state.onKeyDown(event)) {
return true;
}
break;
case KeyEvent.ACTION_UP:
Log.i(TAG, "KeyEvent Up");
if (state.onKeyUp(event)) {
return true;
}
@@ -1055,23 +1066,24 @@ public abstract class NativeActivity extends Activity {
@TargetApi(Build.VERSION_CODES.N)
void sendMouseDelta(float dx, float dy) {
NativeApp.mouseDelta(dx, dy);
// Ignore zero deltas.
if (Math.abs(dx) > 0.001 || Math.abs(dx) > 0.001) {
NativeApp.mouseDelta(dx, dy);
}
}
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public boolean onGenericMotionEvent(MotionEvent event) {
// Log.d(TAG, "onGenericMotionEvent: " + event);
// Log.i(TAG, "NativeActivity onGenericMotionEvent: " + event);
if (InputDeviceState.inputSourceIsJoystick(event.getSource())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
InputDeviceState state = getInputDeviceState(event);
if (state == null) {
Log.w(TAG, "Joystick event but failed to get input device state.");
return super.onGenericMotionEvent(event);
}
state.onJoystickMotion(event);
return true;
InputDeviceState state = getInputDeviceState(event);
if (state == null) {
Log.w(TAG, "Joystick event but failed to get input device state.");
return super.onGenericMotionEvent(event);
}
state.onJoystickMotion(event);
return true;
}
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -1081,15 +1093,38 @@ public abstract class NativeActivity extends Activity {
float dy = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
sendMouseDelta(dx, dy);
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
Log.i(TAG, "Action Hover Move");
// process the mouse hover movement...
NativeApp.mouse(event.getX(), event.getY(), 0, 0);
return true;
case MotionEvent.ACTION_SCROLL:
float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
float scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
Log.i(TAG, "Action Scroll: " + scrollX + " " + scrollY);
NativeApp.mouseWheelEvent(scrollX, scrollY);
return true;
}
}
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
// process the mouse hover movement...
return true;
case MotionEvent.ACTION_SCROLL:
NativeApp.mouseWheelEvent(event.getAxisValue(MotionEvent.AXIS_HSCROLL), event.getAxisValue(MotionEvent.AXIS_VSCROLL));
return true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int button = event.getActionButton();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_BUTTON_PRESS: {
Log.i(TAG, "action button press: button: " + button);
useModernMouseEvents = true;
NativeApp.mouse(event.getX(), event.getY(), button, 1);
return true;
}
case MotionEvent.ACTION_BUTTON_RELEASE: {
Log.i(TAG, "action button release: button: " + button);
NativeApp.mouse(event.getX(), event.getY(), button, 2);
return true;
}
default:
break;
}
}
return super.onGenericMotionEvent(event);
@@ -50,6 +50,7 @@ public class NativeApp {
public static native void accelerometer(float x, float y, float z);
public static native void mouse(float x, float y, int button, int action);
public static native void mouseDelta(float x, float y);
public static native void sendMessageFromJava(String msg, String arg);
public static native void sendRequestResult(int seqID, boolean result, String value, int iValue);
@@ -17,6 +17,7 @@ import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -60,11 +61,49 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
return ev.getToolType(pointer);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
private void onMouseEventMotion(final MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (NativeActivity.useModernMouseEvents) {
return;
}
Log.i(TAG, "GL motion action down. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 1, 1);
break;
}
case MotionEvent.ACTION_UP: {
if (NativeActivity.useModernMouseEvents) {
return;
}
Log.i(TAG, "GL motion action up. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 1, 2);
break;
}
case MotionEvent.ACTION_MOVE: {
// This still needs handling here, even if new events are used.
Log.i(TAG, "GL motion action move. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 0, 0);
break;
}
default: {
Log.i(TAG, "Unhandled modern mouse action: " + ev.getAction());
break;
}
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent ev) {
boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && NativeSurfaceView.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
// This is where workable mouse support arrived.
onMouseEventMotion(ev);
return true;
}
boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
for (int i = 0; i < ev.getPointerCount(); i++) {
int pid = ev.getPointerId(i);
int code = 0;
@@ -16,6 +16,7 @@ import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -66,15 +67,63 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene
if ((ev.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
float dx = ev.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float dy = ev.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
Log.i(TAG, "Mouse delta: " + dx + " " + dy);
NativeApp.mouseDelta(dx, dy);
}
}
public static boolean isFromSource(final InputEvent ev, int source) {
return (ev.getSource() & source) == source;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
private void onMouseEventMotion(final MotionEvent ev) {
Log.i(TAG, "motion mouse event");
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (NativeActivity.useModernMouseEvents) {
return;
}
Log.i(TAG, "Surface Action down. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 1, 1);
break;
}
case MotionEvent.ACTION_UP: {
if (NativeActivity.useModernMouseEvents) {
return;
}
Log.i(TAG, "Surface Action up. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 1, 2);
break;
}
case MotionEvent.ACTION_MOVE: {
// This still needs handling here, even if new events are used.
Log.i(TAG, "Surface Action move. button state: " + ev.getButtonState());
NativeApp.mouse(ev.getX(), ev.getY(), 0, 0);
break;
}
default: {
Log.i(TAG, "Unhandled modern mouse action: " + ev.getAction());
break;
}
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent ev) {
boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
// This is where workable mouse support arrived.
// Also, skip processing if useModernMouseEvents is on.
if (NativeActivity.useModernMouseEvents) {
return true;
}
onMouseEventMotion(ev);
return true;
}
// Log.i(TAG, "processing touch event");
boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
for (int i = 0; i < ev.getPointerCount(); i++) {
int pid = ev.getPointerId(i);
int code = 0;
@@ -85,11 +134,13 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
Log.i(TAG, "ACTION_DOWN");
if (ev.getActionIndex() == i)
code = 2;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
Log.i(TAG, "ACTION_UP");
if (ev.getActionIndex() == i)
code = 4;
break;
@@ -109,7 +160,6 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene
int tool = getToolType(ev, i);
code |= tool << 10; // We use the Android tool type codes
}
// Can't use || due to short circuit evaluation
NativeApp.touch(ev.getX(i), ev.getY(i), code, pid);
}
}