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++) {
touch.write[i].id = p_points[i].id;
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
@ -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_relative(p_points[idx].pos - touch[i].pos);
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);
touch.write[i].pos = p_points[idx].pos;
}

View File

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

View File

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

View File

@ -62,6 +62,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private final SparseIntArray mJoystickIds = new SparseIntArray(4);
private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4);
private final HashSet<Integer> mHardwareKeyboardIds = new HashSet<>();
private final GodotRenderView mRenderView;
private final InputManager mInputManager;
@ -114,6 +115,10 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
rotaryInputAxis = axis;
}
boolean hasHardwareKeyboard() {
return !mHardwareKeyboardIds.isEmpty();
}
private boolean isKeyEventGameDevice(int source) {
// Note that keyboards are often (SOURCE_KEYBOARD | 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) {
lastSeenToolType = event.getToolType(0);
lastSeenToolType = getEventToolType(event);
this.scaleGestureDetector.onTouchEvent(event);
if (this.gestureDetector.onTouchEvent(event)) {
@ -221,7 +226,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
public boolean onGenericMotionEvent(MotionEvent event) {
lastSeenToolType = event.getToolType(0);
lastSeenToolType = getEventToolType(event);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && gestureDetector.onGenericMotionEvent(event)) {
// The gesture detector has handled the event.
@ -310,11 +315,17 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
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
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
if (!device.supportsSource(InputDevice.SOURCE_GAMEPAD) &&
!device.supportsSource(InputDevice.SOURCE_JOYSTICK)) {
return;
}
@ -359,6 +370,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
@Override
public void onInputDeviceRemoved(int deviceId) {
mHardwareKeyboardIds.remove(deviceId);
// Check if the device has not been already removed
if (mJoystickIds.indexOfKey(deviceId) < 0) {
return;
@ -440,50 +453,52 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return button;
}
static boolean isMouseEvent(MotionEvent event) {
return isMouseEvent(event.getSource());
private static int getEventToolType(MotionEvent event) {
return event.getPointerCount() > 0 ? event.getToolType(0) : MotionEvent.TOOL_TYPE_UNKNOWN;
}
private static boolean isMouseEvent(int eventSource) {
boolean mouseSource = ((eventSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) || ((eventSource & 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);
static boolean isMouseEvent(MotionEvent event) {
int toolType = getEventToolType(event);
int eventSource = event.getSource();
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) {
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)) {
return handleMouseEvent(event);
return handleMouseEvent(event, eventActionOverride, doubleTap);
}
return handleTouchEvent(event);
return handleTouchEvent(event, eventActionOverride, doubleTap);
}
static boolean handleMotionEvent(int eventSource, int eventAction, int buttonsMask, float x, float y) {
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();
private static float getEventTiltX(MotionEvent event) {
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation();
@ -493,8 +508,39 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
float tiltMult = (float)Math.sin(tilt);
// To be consistent with expected tilt.
final float tiltX = (float)-Math.sin(orientation) * tiltMult;
final float tiltY = (float)Math.cos(orientation) * tiltMult;
return (float)-Math.sin(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 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) {
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) {
return handleMouseEvent(eventAction, buttonsMask, x, y, 0, 0, false, false);
}
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, boolean sourceMouseRelative) {
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, float pressure, float tiltX, float tiltY) {
@ -563,37 +605,39 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
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();
if (pointerCount == 0) {
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++) {
positions[i * 3 + 0] = event.getPointerId(i);
positions[i * 3 + 1] = event.getX(i);
positions[i * 3 + 2] = event.getY(i);
positions[i * 6 + 0] = event.getPointerId(i);
positions[i * 6 + 1] = event.getX(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());
return handleTouchEvent(action, actionPointerId, pointerCount, positions, false);
}
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) {
switch (eventActionOverride) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN: {
GodotLib.dispatchTouchEvent(eventAction, actionPointerId, pointerCount, positions, doubleTap);
GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
return true;
}
}

View File

@ -294,11 +294,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JN
Vector<AndroidInputHandler::TouchPos> points;
for (int i = 0; i < pointer_count; i++) {
jfloat p[3];
env->GetFloatArrayRegion(position, i * 3, 3, p);
jfloat p[6];
env->GetFloatArrayRegion(position, i * 6, 6, p);
AndroidInputHandler::TouchPos tp;
tp.pos = Point2(p[1], p[2]);
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);
}