ANDROID: Support two finger touch mouse wheel

This commit is contained in:
antoniou79 2023-08-11 16:51:29 +03:00
parent ba815489f8
commit b347be081c
2 changed files with 211 additions and 59 deletions

View File

@ -1139,8 +1139,21 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case JE_MOUSE_WHEEL_UP:
// Rolling wheel upwards
ev0.type = Common::EVENT_WHEELUP;
ev0.mouse.x = arg1;
ev0.mouse.y = arg2;
if (arg3 == 1 && _touch_mode == TOUCH_MODE_TOUCHPAD) {
// event comes from touch gesture
if (_touch_pt_scroll.x == -1 && _touch_pt_scroll.y == -1) {
_touch_pt_scroll.x = arg1;
_touch_pt_scroll.y = arg2;
return;
}
ev0.mouse.x = (arg1 - _touch_pt_scroll.x) * 100 / _touchpad_scale;
ev0.mouse.y = (arg2 - _touch_pt_scroll.y) * 100 / _touchpad_scale;
ev0.mouse += _touch_pt_down;
} else {
ev0.mouse.x = arg1;
ev0.mouse.y = arg2;
}
// ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
pushEvent(ev0);
break;
@ -1148,8 +1161,21 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case JE_MOUSE_WHEEL_DOWN:
// Rolling wheel downwards
ev0.type = Common::EVENT_WHEELDOWN;
ev0.mouse.x = arg1;
ev0.mouse.y = arg2;
if (arg3 == 1 && _touch_mode == TOUCH_MODE_TOUCHPAD) {
// event comes from touch gesture
if (_touch_pt_scroll.x == -1 && _touch_pt_scroll.y == -1) {
_touch_pt_scroll.x = arg1;
_touch_pt_scroll.y = arg2;
return;
}
ev0.mouse.x = (arg1 - _touch_pt_scroll.x) * 100 / _touchpad_scale;
ev0.mouse.y = (arg2 - _touch_pt_scroll.y) * 100 / _touchpad_scale;
ev0.mouse += _touch_pt_down;
} else {
ev0.mouse.x = arg1;
ev0.mouse.y = arg2;
}
// ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
pushEvent(ev0);
break;

View File

@ -11,6 +11,8 @@ import androidx.annotation.NonNull;
import java.lang.ref.WeakReference;
import static org.scummvm.scummvm.ScummVMEventsBase.JE_MOUSE_WHEEL_DOWN;
import static org.scummvm.scummvm.ScummVMEventsBase.JE_MOUSE_WHEEL_UP;
import static org.scummvm.scummvm.ScummVMEventsBase.JE_MULTI;
public class MultitouchHelper {
@ -29,6 +31,11 @@ public class MultitouchHelper {
private int _cachedActionEventOnPointer2DownX;
private int _cachedActionEventOnPointer2DownY;
//
private int _cachedActionEventOnPointer1DownX;
private int _cachedActionEventOnPointer1DownY;
// The "level" of multitouch that is detected.
// We do not support downgrading a level, ie. if a three finger multitouch is detected,
// then raising one finger will void the multitouch session,
@ -38,25 +45,51 @@ public class MultitouchHelper {
// rather than upgrade it to three fingers multitouch.
// INFO for this purpose we need to allow some limited time limit (delay _kLevelDecisionDelayMs) before deciding
// if the user did a two finger multitouch or intents to do a three finger multitouch
// Valid values for _multitouchLevel: 0, 2, 3
// Valid values for _multitouchLevel: MULTITOUCH_UNDECIDED, MULTITOUCH_TWO_FINGERS, MULTITOUCH_THREE_FINGERS
private final int MULTITOUCH_UNDECIDED = 0;
private final int MULTITOUCH_TWO_FINGERS = 2;
private final int MULTITOUCH_THREE_FINGERS = 3;
private int _multitouchLevel;
private final int _kLevelDecisionDelayMs = 400; // in milliseconds
private final int _kTouchMouseWheelDecisionDelayMs = 260; // in milliseconds - NOTE: Keep it significantly lower than _kLevelDecisionDelayMs
// messages for MultitouchHelperHandler
final static int MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT = 1;
final static int MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT = 2;
final static int MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT = 3;
final private MultitouchHelper.MultitouchHelperHandler _multiTouchLevelUpgradeHandler = new MultitouchHelper.MultitouchHelperHandler(this);
// Scroll handling variables (calling it "Mouse Wheel" as SCROLL event on Android touch interface refers to moving around a finger on the touch surfaces)
private final int TOUCH_MOUSE_WHEEL_UNDECIDED = 0;
private final int TOUCH_MOUSE_WHEEL_ACTIVE = 1;
private final int TOUCH_MOUSE_WHEEL_NOT_HAPPENING = 3;
// Both fingers need to have moved up or down to enter the scroll (mouse wheel) mode
// The difference from the original positions (for both fingers respectively) is averaged and compared to the threshold below
// The decision to enter scroll mode happens as long as there are two fingers down but we're still undecided,
// (ie. undecided about whether this is a two finger event OR a third finger will follow OR it is a scroll event)
private final int MOVE_THRESHOLD_FOR_TOUCH_MOUSE_WHEEL_DECISION = 20;
private final int MOVE_THRESHOLD_FOR_SEND_TOUCH_MOUSE_WHEEL_EVENT = 30;
// 0: Undecided for scrolling (mouse wheel)
// 1: "scrolling" mode active
// 2: no "scrolling" (mouse wheel) (decided)
// Scrolling (mouse wheel) mode is mutually exclusive with the rest of the multi-touch modes,
// we can either send mouse wheel events or mouse click events in a multitouch session.
private int _touchMouseWheelDecisionLevel = 0;
// constructor
public MultitouchHelper(ScummVM scummvm) {
_scummvm = scummvm;
_multitouchMode = false;
_multitouchLevel = 0;
_multitouchLevel = MULTITOUCH_UNDECIDED;
_candidateStartOfMultitouchSession = false;
_touchMouseWheelDecisionLevel = TOUCH_MOUSE_WHEEL_UNDECIDED;
resetPointers();
}
@ -64,6 +97,10 @@ public class MultitouchHelper {
_firstPointerId = -1;
_secondPointerId = -1;
_thirdPointerId = -1;
_cachedActionEventOnPointer1DownX = -1;
_cachedActionEventOnPointer1DownY = -1;
_cachedActionEventOnPointer2DownX = -1;
_cachedActionEventOnPointer2DownY = -1;
}
public boolean isMultitouchMode() {
@ -73,6 +110,11 @@ public class MultitouchHelper {
return _multitouchLevel;
}
public int getTouchMouseWheelDecisionLevel() {
return _touchMouseWheelDecisionLevel;
}
public boolean isTouchMouseWheel() { return getTouchMouseWheelDecisionLevel() == TOUCH_MOUSE_WHEEL_ACTIVE; }
public void setMultitouchMode(boolean enabledFlg) {
_multitouchMode = enabledFlg;
}
@ -80,6 +122,9 @@ public class MultitouchHelper {
_multitouchLevel = mtlevel;
}
public void setTouchMouseWheelDecisionLevel(int scrlevel) {
_touchMouseWheelDecisionLevel = scrlevel;
}
// TODO Maybe for consistency purposes, maybe sent all (important) UP events that were not sent, when ending a multitouch session?
public boolean handleMotionEvent(final MotionEvent event) {
@ -97,23 +142,29 @@ public class MultitouchHelper {
if (maskedAction == MotionEvent.ACTION_DOWN) {
// start of a multitouch session! one finger down -- this is sent for the first pointer who touches the screen
resetPointers();
setMultitouchLevel(0);
setMultitouchLevel(MULTITOUCH_UNDECIDED);
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_UNDECIDED);
setMultitouchMode(false);
_candidateStartOfMultitouchSession = true;
_multiTouchLevelUpgradeHandler.clear();
// _multiTouchLevelUpgradeHandler.sendMessageDelayed(_multiTouchLevelUpgradeHandler.obtainMessage(MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT), _kLevelDecisionDelayMs);
pointerIndex = 0;
_firstPointerId = event.getPointerId(pointerIndex);
// TODO - do we want this as true?
_cachedActionEventOnPointer1DownX = (int) event.getX(pointerIndex);;
_cachedActionEventOnPointer1DownY = (int) event.getY(pointerIndex);;
return false;
} else if (maskedAction == MotionEvent.ACTION_CANCEL) {
resetPointers();
setMultitouchLevel(0);
setMultitouchLevel(MULTITOUCH_UNDECIDED);
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_UNDECIDED);
setMultitouchMode(false);
_multiTouchLevelUpgradeHandler.clear();
return true;
} else if (maskedAction == MotionEvent.ACTION_OUTSIDE) {
return false;
}
if (event.getPointerCount() > 1 && event.getPointerCount() < 4) {
@ -129,8 +180,9 @@ public class MultitouchHelper {
if (event.getPointerCount() == 2) {
_secondPointerId = event.getPointerId(pointerIndex);
if (getMultitouchLevel() == 0) {
if (getMultitouchLevel() == MULTITOUCH_UNDECIDED) {
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT);
if (pointerIndex != -1) {
_cachedActionEventOnPointer2DownX = (int) event.getX(pointerIndex);
_cachedActionEventOnPointer2DownY = (int) event.getY(pointerIndex);
@ -140,6 +192,8 @@ public class MultitouchHelper {
}
// Allow for some time before deciding a two finger touch event, since the user might be going for a three finger touch event
_multiTouchLevelUpgradeHandler.sendMessageDelayed(_multiTouchLevelUpgradeHandler.obtainMessage(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT), _kLevelDecisionDelayMs);
// Also allow for (less) time to check if this is a two-finger "mouse-wheel" event
_multiTouchLevelUpgradeHandler.sendMessageDelayed(_multiTouchLevelUpgradeHandler.obtainMessage(MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT), _kTouchMouseWheelDecisionDelayMs);
// Return as event "handled" here
// while we wait for the decision to be made for the level of multitouch (two or three)
//
@ -153,15 +207,16 @@ public class MultitouchHelper {
} else if (event.getPointerCount() == 3) {
_thirdPointerId = event.getPointerId(pointerIndex);
if (getMultitouchLevel() == 0) {
setMultitouchLevel(3);
if (getMultitouchLevel() == MULTITOUCH_UNDECIDED) {
setMultitouchLevel(MULTITOUCH_THREE_FINGERS);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT);
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_NOT_HAPPENING);
}
}
} else {
if (event.getPointerCount() == 2) {
// we prioritize the second pointer/ finger
pointerIndex = event.findPointerIndex(_secondPointerId);
if (pointerIndex != -1) {
actionEventX = (int)event.getX(pointerIndex);
actionEventY = (int)event.getY(pointerIndex);
@ -170,29 +225,80 @@ public class MultitouchHelper {
actionEventY = -1;
}
// Fast trigger an ACTION_POINTER_DOWN if:
// - we were not yet decided on which level to use or
// - a finger got up (from 3 to 2, shouldn't happen) or
// - our main finger moved from cached position
if (getMultitouchLevel() == 0
&& (maskedAction == MotionEvent.ACTION_POINTER_UP ||
(maskedAction == MotionEvent.ACTION_MOVE &&
(actionEventX != _cachedActionEventOnPointer2DownX ||
actionEventY != _cachedActionEventOnPointer2DownY)))) {
if (getMultitouchLevel() == MULTITOUCH_UNDECIDED) {
// Fast trigger an ACTION_POINTER_DOWN if:
// - we were not yet decided on which level to use
// AND either:
// - a finger got up (from 3 to 2, shouldn't happen) or
// - our main finger (second finger down) moved from cached position
if (maskedAction == MotionEvent.ACTION_POINTER_UP
|| (maskedAction == MotionEvent.ACTION_MOVE
&& (actionEventX != _cachedActionEventOnPointer2DownX
|| actionEventY != _cachedActionEventOnPointer2DownY))) {
setMultitouchLevel(2);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT);
setMultitouchLevel(MULTITOUCH_TWO_FINGERS);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT);
// Checking if we can decide on a touch mouse wheel mode session
int firstPointerIndex = event.findPointerIndex(_firstPointerId);
int actionEventFirstPointerCoordY = -1;
if (firstPointerIndex != -1) {
actionEventFirstPointerCoordY = (int) event.getY(firstPointerIndex);
}
// send the missing pointer down event first
_scummvm.pushEvent(JE_MULTI,
event.getPointerCount(),
MotionEvent.ACTION_POINTER_DOWN,
actionEventX,
actionEventY,
0, 0);
if (maskedAction == MotionEvent.ACTION_MOVE) {
// Decide Scroll (touch mouse wheel) if:
// - we were not yet decided on which level to use
// - and two fingers are down (but not because we went from 3 to 2)
// - and it's a move event
// - and the movement distance of both fingers on y axis is around >= MOVE_THRESHOLD_FOR_TOUCH_MOUSE_WHEEL_DECISION
// (plus some other qualifying checks to determine significant and similar movement on both fingers)
// NOTE the movementOfFinger2onY and movementOfFinger1onY gets higher (on subsequent events)
// if the user keeps moving their fingers (in the same direction),
// since it's in reference to the starting points for the fingers
int movementOfFinger2onY = actionEventY - _cachedActionEventOnPointer2DownY;
int movementOfFinger1onY = actionEventFirstPointerCoordY - _cachedActionEventOnPointer1DownY;
int absMovementOfFinger2onY = Math.abs(movementOfFinger2onY);
int absMovementOfFinger1onY = Math.abs(movementOfFinger1onY);
int absDiffOfMovementOfFingersOnY = Math.abs(movementOfFinger2onY - movementOfFinger1onY);
if (getTouchMouseWheelDecisionLevel() == TOUCH_MOUSE_WHEEL_UNDECIDED
&& (movementOfFinger2onY > 0 && movementOfFinger1onY > 0) || (movementOfFinger2onY < 0 && movementOfFinger1onY < 0)
&& absDiffOfMovementOfFingersOnY < MOVE_THRESHOLD_FOR_TOUCH_MOUSE_WHEEL_DECISION ) {
if ((absMovementOfFinger2onY + absMovementOfFinger1onY) / 2 >= MOVE_THRESHOLD_FOR_TOUCH_MOUSE_WHEEL_DECISION) {
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_ACTIVE);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT);
} else {
// ignore this move event but don't forward it (return true as "event handled")
// there's still potential to be a scroll (touch mouse wheel) event, with accumulated movement
//
// Also downgrade the multitouch level to undecided to re-enter this code segment next time
// (the "countdown" for three-finger touch decision is not resumed)
setMultitouchLevel(MULTITOUCH_UNDECIDED);
return true;
}
} else {
setMultitouchLevel(MULTITOUCH_UNDECIDED);
return true;
}
} else {
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_NOT_HAPPENING);
_multiTouchLevelUpgradeHandler.removeMessages(MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT);
}
// End of: Checking if we can decide on a touch mouse wheel mode session
if (getTouchMouseWheelDecisionLevel() != TOUCH_MOUSE_WHEEL_ACTIVE) {
// send the missing pointer down event first, before sending the actual current move event below
_scummvm.pushEvent(JE_MULTI,
event.getPointerCount(),
MotionEvent.ACTION_POINTER_DOWN,
actionEventX,
actionEventY,
0, 0);
}
}
}
} else if (event.getPointerCount() == 3) {
// we prioritize the third pointer/ finger
pointerIndex = event.findPointerIndex(_thirdPointerId);
@ -213,13 +319,35 @@ public class MultitouchHelper {
// we are only concerned for events with fingers down equal to the decided level of multitouch session
if (getMultitouchLevel() == event.getPointerCount()) {
// arg1 will be the number of fingers down in the MULTI event we send to events.cpp
_scummvm.pushEvent(JE_MULTI,
event.getPointerCount(),
event.getAction(),
actionEventX,
actionEventY,
0, 0);
if ((isTouchMouseWheel()) ) {
if (maskedAction == MotionEvent.ACTION_MOVE && pointerIndex == event.findPointerIndex(_secondPointerId)) {
// The co-ordinates sent with the event are the original touch co-ordinates of the first finger
// The mouse cursor should not move around while touch-mouse-wheel scrolling
// Also for simplification we only use the move events for the second finger
// and skip non-significant movements.
int movementOfFinger2onY = actionEventY - _cachedActionEventOnPointer2DownY;
if (Math.abs(movementOfFinger2onY) > MOVE_THRESHOLD_FOR_SEND_TOUCH_MOUSE_WHEEL_EVENT) {
_scummvm.pushEvent((movementOfFinger2onY > 0 ) ? JE_MOUSE_WHEEL_UP : JE_MOUSE_WHEEL_DOWN,
_cachedActionEventOnPointer1DownX,
_cachedActionEventOnPointer1DownY,
1, // This will indicate to the event handling code in native that it comes from touch interface
0,
0, 0);
_cachedActionEventOnPointer2DownY = actionEventY;
}
} // otherwise don't push an event in this case and return true
} else {
// arg1 will be the number of fingers down in the MULTI event we send to events.cpp
// arg2 is the event action
// arg3 and arg4 are the X,Y coordinates for the "active pointer" index, which is the last finger down
// (the second in two-fingers touch, or the third in a three-fingers touch mode)
_scummvm.pushEvent(JE_MULTI,
event.getPointerCount(),
event.getAction(),
actionEventX,
actionEventY,
0, 0);
}
}
}
return true;
@ -240,7 +368,6 @@ public class MultitouchHelper {
}
}
// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occur”) based on:
// https://stackoverflow.com/a/27826094
public static class MultitouchHelperHandler extends Handler {
@ -266,26 +393,25 @@ public class MultitouchHelper {
}
private void handle_MTHH_Message(final Message msg) {
if (msg.what == MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT) {
if (getMultitouchLevel() == 0) {
// window of allowing upgrade to level 3 timed out, decide level two.
setMultitouchLevel(2);
if ((msg.what == MSG_MT_UPGRADE_TO_LEVEL_3_TIMEDOUT && getMultitouchLevel() == MULTITOUCH_UNDECIDED)
|| (msg.what == MSG_MT_CHECK_FOR_TOUCH_MOUSE_WHEEL_TIMEDOUT
&& (getMultitouchLevel() == MULTITOUCH_UNDECIDED
|| (getMultitouchLevel() == 2 && getTouchMouseWheelDecisionLevel() == TOUCH_MOUSE_WHEEL_UNDECIDED)))) {
// Either:
// - window of allowing upgrade to level 3 (three fingers) timed out
// - or window of allowing time for checking for scroll (touch mouse wheel) timed out
// decide level 2 (two fingers).
setMultitouchLevel(MULTITOUCH_TWO_FINGERS);
setTouchMouseWheelDecisionLevel(TOUCH_MOUSE_WHEEL_NOT_HAPPENING);
// send the delayed pointer down event
_scummvm.pushEvent(JE_MULTI,
2,
MotionEvent.ACTION_POINTER_DOWN,
_cachedActionEventOnPointer2DownX,
_cachedActionEventOnPointer2DownY,
0, 0);
}
// send the delayed pointer down event
_scummvm.pushEvent(JE_MULTI,
2,
MotionEvent.ACTION_POINTER_DOWN,
_cachedActionEventOnPointer2DownX,
_cachedActionEventOnPointer2DownY,
0, 0);
}
// else if (msg.what == MSG_MT_DECIDE_MULTITOUCH_SESSION_TIMEDOUT) {
// if (_candidateStartOfMultitouchSession && !isMultitouchMode()) {
// // window of considering touch as start of multitouch event timed out. Clear the candidate flag.
// _candidateStartOfMultitouchSession = false;
// }
// }
}
public void clearEventHandler() {