Input logic cleanup:

- Fix invalid detection of mouse input. Prioritize using the event tool type to detect the type of the event, and only use the event source as fallback.

- Ensure that pressure and tilt information is passed for touch drag events

- Consolidate logic and remove redundant methods

- Improve the logic to detect when external hardware keyboards are connected to the device
This commit is contained in:
Fredia Huya-Kouadio 2024-05-19 10:06:35 -07:00
parent daa81bbb7d
commit 625b92e3cd
6 changed files with 133 additions and 126 deletions

View File

@ -176,6 +176,8 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const
for (int i = 0; i < p_points.size(); i++) { for (int i = 0; i < p_points.size(); i++) {
touch.write[i].id = p_points[i].id; touch.write[i].id = p_points[i].id;
touch.write[i].pos = p_points[i].pos; touch.write[i].pos = p_points[i].pos;
touch.write[i].pressure = p_points[i].pressure;
touch.write[i].tilt = p_points[i].tilt;
} }
//send touch //send touch
@ -208,6 +210,8 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const
ev->set_position(p_points[idx].pos); ev->set_position(p_points[idx].pos);
ev->set_relative(p_points[idx].pos - touch[i].pos); ev->set_relative(p_points[idx].pos - touch[i].pos);
ev->set_relative_screen_position(ev->get_relative()); ev->set_relative_screen_position(ev->get_relative());
ev->set_pressure(p_points[idx].pressure);
ev->set_tilt(p_points[idx].tilt);
Input::get_singleton()->parse_input_event(ev); Input::get_singleton()->parse_input_event(ev);
touch.write[i].pos = p_points[idx].pos; touch.write[i].pos = p_points[idx].pos;
} }

View File

@ -42,6 +42,8 @@ public:
struct TouchPos { struct TouchPos {
int id = 0; int id = 0;
Point2 pos; Point2 pos;
float pressure = 0;
Vector2 tilt;
}; };
struct MouseEventInfo { struct MouseEventInfo {

View File

@ -266,8 +266,13 @@ public class GodotEditText extends EditText {
boolean hasHardwareKeyboard() { boolean hasHardwareKeyboard() {
Configuration config = getResources().getConfiguration(); Configuration config = getResources().getConfiguration();
return config.keyboard != Configuration.KEYBOARD_NOKEYS && boolean hasHardwareKeyboardConfig = config.keyboard != Configuration.KEYBOARD_NOKEYS &&
config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
if (hasHardwareKeyboardConfig) {
return true;
}
return mRenderView.getInputHandler().hasHardwareKeyboard();
} }
// =========================================================== // ===========================================================

View File

@ -62,7 +62,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private var pointerCaptureInProgress = false private var pointerCaptureInProgress = false
override fun onDown(event: MotionEvent): Boolean { override fun onDown(event: MotionEvent): Boolean {
GodotInputHandler.handleMotionEvent(event.source, MotionEvent.ACTION_DOWN, event.buttonState, event.x, event.y, nextDownIsDoubleTap) GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap)
nextDownIsDoubleTap = false nextDownIsDoubleTap = false
return true return true
} }
@ -82,20 +82,14 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
} }
// Cancel the previous down event // Cancel the previous down event
GodotInputHandler.handleMotionEvent( GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL)
event.source,
MotionEvent.ACTION_CANCEL,
event.buttonState,
event.x,
event.y
)
// Turn a context click into a single tap right mouse button click. // Turn a context click into a single tap right mouse button click.
GodotInputHandler.handleMouseEvent( GodotInputHandler.handleMouseEvent(
event,
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_DOWN,
MotionEvent.BUTTON_SECONDARY, MotionEvent.BUTTON_SECONDARY,
event.x, false
event.y
) )
contextClickInProgress = true contextClickInProgress = true
} }
@ -107,16 +101,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (!hasCapture) { if (!hasCapture) {
// Dispatch a mouse relative ACTION_UP event to signal the end of the capture // Dispatch a mouse relative ACTION_UP event to signal the end of the capture
GodotInputHandler.handleMouseEvent( GodotInputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true)
MotionEvent.ACTION_UP,
0,
0f,
0f,
0f,
0f,
false,
true
)
} }
pointerCaptureInProgress = hasCapture pointerCaptureInProgress = hasCapture
} }
@ -139,26 +124,11 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
return true return true
} }
val sourceMouseRelative = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)
} else {
false
}
if (pointerCaptureInProgress || dragInProgress || contextClickInProgress) { if (pointerCaptureInProgress || dragInProgress || contextClickInProgress) {
if (contextClickInProgress || GodotInputHandler.isMouseEvent(event)) { if (contextClickInProgress || GodotInputHandler.isMouseEvent(event)) {
// This may be an ACTION_BUTTON_RELEASE event which we don't handle, // This may be an ACTION_BUTTON_RELEASE event which we don't handle,
// so we convert it to an ACTION_UP event. // so we convert it to an ACTION_UP event.
GodotInputHandler.handleMouseEvent( GodotInputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP)
MotionEvent.ACTION_UP,
event.buttonState,
event.x,
event.y,
0f,
0f,
false,
sourceMouseRelative
)
} else { } else {
GodotInputHandler.handleTouchEvent(event) GodotInputHandler.handleTouchEvent(event)
} }
@ -173,21 +143,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private fun onActionMove(event: MotionEvent): Boolean { private fun onActionMove(event: MotionEvent): Boolean {
if (contextClickInProgress) { if (contextClickInProgress) {
val sourceMouseRelative = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { GodotInputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false)
event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)
} else {
false
}
GodotInputHandler.handleMouseEvent(
event.actionMasked,
MotionEvent.BUTTON_SECONDARY,
event.x,
event.y,
0f,
0f,
false,
sourceMouseRelative
)
return true return true
} }
return false return false
@ -197,7 +153,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (event.actionMasked == MotionEvent.ACTION_UP) { if (event.actionMasked == MotionEvent.ACTION_UP) {
nextDownIsDoubleTap = false nextDownIsDoubleTap = false
GodotInputHandler.handleMotionEvent(event) GodotInputHandler.handleMotionEvent(event)
} else if (event.actionMasked == MotionEvent.ACTION_MOVE && panningAndScalingEnabled == false) { } else if (event.actionMasked == MotionEvent.ACTION_MOVE && !panningAndScalingEnabled) {
GodotInputHandler.handleMotionEvent(event) GodotInputHandler.handleMotionEvent(event)
} }
@ -219,13 +175,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (dragInProgress) { if (dragInProgress) {
if (originEvent != null) { if (originEvent != null) {
// Cancel the drag // Cancel the drag
GodotInputHandler.handleMotionEvent( GodotInputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL)
originEvent.source,
MotionEvent.ACTION_CANCEL,
originEvent.buttonState,
originEvent.x,
originEvent.y
)
} }
dragInProgress = false dragInProgress = false
} }

View File

@ -62,6 +62,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private final SparseIntArray mJoystickIds = new SparseIntArray(4); private final SparseIntArray mJoystickIds = new SparseIntArray(4);
private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4); private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4);
private final HashSet<Integer> mHardwareKeyboardIds = new HashSet<>();
private final GodotRenderView mRenderView; private final GodotRenderView mRenderView;
private final InputManager mInputManager; private final InputManager mInputManager;
@ -114,6 +115,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
rotaryInputAxis = axis; rotaryInputAxis = axis;
} }
boolean hasHardwareKeyboard() {
return !mHardwareKeyboardIds.isEmpty();
}
private boolean isKeyEventGameDevice(int source) { private boolean isKeyEventGameDevice(int source) {
// Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD) // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)
if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))
@ -195,7 +200,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
} }
public boolean onTouchEvent(final MotionEvent event) { public boolean onTouchEvent(final MotionEvent event) {
lastSeenToolType = event.getToolType(0); lastSeenToolType = getEventToolType(event);
this.scaleGestureDetector.onTouchEvent(event); this.scaleGestureDetector.onTouchEvent(event);
if (this.gestureDetector.onTouchEvent(event)) { if (this.gestureDetector.onTouchEvent(event)) {
@ -221,7 +226,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
} }
public boolean onGenericMotionEvent(MotionEvent event) { public boolean onGenericMotionEvent(MotionEvent event) {
lastSeenToolType = event.getToolType(0); lastSeenToolType = getEventToolType(event);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
// The gesture detector has handled the event. // The gesture detector has handled the event.
@ -310,11 +315,17 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return; return;
} }
int sources = device.getSources(); // Device may be an external keyboard; store the device id
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
device.supportsSource(InputDevice.SOURCE_KEYBOARD) &&
device.isExternal() &&
device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
mHardwareKeyboardIds.add(deviceId);
}
// Device may not be a joystick or gamepad // Device may not be a joystick or gamepad
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD && if (!device.supportsSource(InputDevice.SOURCE_GAMEPAD) &&
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { !device.supportsSource(InputDevice.SOURCE_JOYSTICK)) {
return; return;
} }
@ -359,6 +370,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
@Override @Override
public void onInputDeviceRemoved(int deviceId) { public void onInputDeviceRemoved(int deviceId) {
mHardwareKeyboardIds.remove(deviceId);
// Check if the device has not been already removed // Check if the device has not been already removed
if (mJoystickIds.indexOfKey(deviceId) < 0) { if (mJoystickIds.indexOfKey(deviceId) < 0) {
return; return;
@ -440,50 +453,52 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return button; return button;
} }
static boolean isMouseEvent(MotionEvent event) { private static int getEventToolType(MotionEvent event) {
return isMouseEvent(event.getSource()); return event.getPointerCount() > 0 ? event.getToolType(0) : MotionEvent.TOOL_TYPE_UNKNOWN;
} }
private static boolean isMouseEvent(int eventSource) { static boolean isMouseEvent(MotionEvent event) {
boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS); int toolType = getEventToolType(event);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int eventSource = event.getSource();
mouseSource = mouseSource || ((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
switch (toolType) {
case MotionEvent.TOOL_TYPE_FINGER:
return false;
case MotionEvent.TOOL_TYPE_MOUSE:
case MotionEvent.TOOL_TYPE_STYLUS:
case MotionEvent.TOOL_TYPE_ERASER:
return true;
case MotionEvent.TOOL_TYPE_UNKNOWN:
default:
boolean mouseSource =
((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ||
((eventSource & (InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_STYLUS)) == InputDevice.SOURCE_STYLUS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mouseSource = mouseSource ||
((eventSource & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
}
return mouseSource;
} }
return mouseSource;
} }
static boolean handleMotionEvent(final MotionEvent event) { static boolean handleMotionEvent(final MotionEvent event) {
return handleMotionEvent(event, event.getActionMasked());
}
static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) {
return handleMotionEvent(event, eventActionOverride, false);
}
static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
if (isMouseEvent(event)) { if (isMouseEvent(event)) {
return handleMouseEvent(event); return handleMouseEvent(event, eventActionOverride, doubleTap);
} }
return handleTouchEvent(event, eventActionOverride, doubleTap);
return handleTouchEvent(event);
} }
static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y) { private static float getEventTiltX(MotionEvent event) {
return handleMotionEvent(eventSource, eventAction, buttonsMask, x, y, false);
}
static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y, boolean doubleTap) {
return handleMotionEvent(eventSource, eventAction, buttonsMask, x, y, 0, 0, doubleTap);
}
static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleTap) {
if (isMouseEvent(eventSource)) {
return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleTap, false);
}
return handleTouchEvent(eventAction, x, y, doubleTap);
}
static boolean handleMouseEvent(final MotionEvent event) {
final int eventAction = event.getActionMasked();
final float x = event.getX();
final float y = event.getY();
final int buttonsMask = event.getButtonState();
final float pressure = event.getPressure();
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise. // Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation(); final float orientation = event.getOrientation();
@ -493,8 +508,39 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
float tiltMult = (float)Math.sin(tilt); float tiltMult = (float)Math.sin(tilt);
// To be consistent with expected tilt. // To be consistent with expected tilt.
final float tiltX = (float)-Math.sin(orientation) * tiltMult; return (float)-Math.sin(orientation) * tiltMult;
final float tiltY = (float)Math.cos(orientation) * tiltMult; }
private static float getEventTiltY(MotionEvent event) {
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation();
// Tilt is zero is perpendicular to the screen and pi/2 is flat on the surface.
final float tilt = event.getAxisValue(MotionEvent.AXIS_TILT);
float tiltMult = (float)Math.sin(tilt);
// To be consistent with expected tilt.
return (float)Math.cos(orientation) * tiltMult;
}
static boolean handleMouseEvent(final MotionEvent event) {
return handleMouseEvent(event, event.getActionMasked());
}
static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) {
return handleMouseEvent(event, eventActionOverride, false);
}
static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
return handleMouseEvent(event, eventActionOverride, event.getButtonState(), doubleTap);
}
static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) {
final float x = event.getX();
final float y = event.getY();
final float pressure = event.getPressure();
float verticalFactor = 0; float verticalFactor = 0;
float horizontalFactor = 0; float horizontalFactor = 0;
@ -516,15 +562,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
sourceMouseRelative = event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE); sourceMouseRelative = event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE);
} }
return handleMouseEvent(eventAction, buttonsMask, x, y, horizontalFactor, verticalFactor, false, sourceMouseRelative, pressure, tiltX, tiltY); return handleMouseEvent(eventActionOverride, buttonMaskOverride, x, y, horizontalFactor, verticalFactor, doubleTap, sourceMouseRelative, pressure, getEventTiltX(event), getEventTiltY(event));
} }
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y) { static boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) {
return handleMouseEvent(eventAction, buttonsMask, x, y, 0, 0, false, false); return handleMouseEvent(eventAction, 0, 0f, 0f, 0f, 0f, false, sourceMouseRelative, 1f, 0f, 0f);
}
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative) {
return handleMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, 1, 0, 0);
} }
static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) { static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
@ -563,37 +605,39 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
} }
static boolean handleTouchEvent(final MotionEvent event) { static boolean handleTouchEvent(final MotionEvent event) {
return handleTouchEvent(event, event.getActionMasked());
}
static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) {
return handleTouchEvent(event, eventActionOverride, false);
}
static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
final int pointerCount = event.getPointerCount(); final int pointerCount = event.getPointerCount();
if (pointerCount == 0) { if (pointerCount == 0) {
return true; return true;
} }
final float[] positions = new float[pointerCount * 3]; // pointerId1, x1, y1, pointerId2, etc... final float[] positions = new float[pointerCount * 6]; // pointerId1, x1, y1, pressure1, tiltX1, tiltY1, pointerId2, etc...
for (int i = 0; i < pointerCount; i++) { for (int i = 0; i < pointerCount; i++) {
positions[i * 3 + 0] = event.getPointerId(i); positions[i * 6 + 0] = event.getPointerId(i);
positions[i * 3 + 1] = event.getX(i); positions[i * 6 + 1] = event.getX(i);
positions[i * 3 + 2] = event.getY(i); positions[i * 6 + 2] = event.getY(i);
positions[i * 6 + 3] = event.getPressure(i);
positions[i * 6 + 4] = getEventTiltX(event);
positions[i * 6 + 5] = getEventTiltY(event);
} }
final int action = event.getActionMasked();
final int actionPointerId = event.getPointerId(event.getActionIndex()); final int actionPointerId = event.getPointerId(event.getActionIndex());
return handleTouchEvent(action, actionPointerId, pointerCount, positions, false); switch (eventActionOverride) {
}
static boolean handleTouchEvent(int eventAction, float x, float y, boolean doubleTap) {
return handleTouchEvent(eventAction, 0, 1, new float[] { 0, x, y }, doubleTap);
}
static boolean handleTouchEvent(int eventAction, int actionPointerId, int pointerCount, float[] positions, boolean doubleTap) {
switch (eventAction) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN: { case MotionEvent.ACTION_POINTER_DOWN: {
GodotLib.dispatchTouchEvent(eventAction, actionPointerId, pointerCount, positions, doubleTap); GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
return true; return true;
} }
} }

View File

@ -294,11 +294,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JN
Vector<AndroidInputHandler::TouchPos> points; Vector<AndroidInputHandler::TouchPos> points;
for (int i = 0; i < pointer_count; i++) { for (int i = 0; i < pointer_count; i++) {
jfloat p[3]; jfloat p[6];
env->GetFloatArrayRegion(position, i * 3, 3, p); env->GetFloatArrayRegion(position, i * 6, 6, p);
AndroidInputHandler::TouchPos tp; AndroidInputHandler::TouchPos tp;
tp.pos = Point2(p[1], p[2]);
tp.id = (int)p[0]; tp.id = (int)p[0];
tp.pos = Point2(p[1], p[2]);
tp.pressure = p[3];
tp.tilt = Vector2(p[4], p[5]);
points.push_back(tp); points.push_back(tp);
} }