Implement full support for mouse input on Android

The smoothing algorithm changed a bit now that I centralized that logic
in a way that can work with all backends.
This commit is contained in:
Henrik Rydgård 2023-12-09 21:40:09 +01:00
parent 904ce4f7e1
commit db4993bfdc
10 changed files with 120 additions and 64 deletions

View File

@ -56,6 +56,7 @@ void NativeTouch(const TouchInput &touch);
bool NativeKey(const KeyInput &key);
void NativeAxis(const AxisInput *axis, size_t count);
void NativeAccelerometer(float tiltX, float tiltY, float tiltZ);
void NativeMouseDelta(float dx, float dy);
// Called when it's process a frame, including rendering. If the device can keep up, this
// will be called sixty times per second. Main thread.

View File

@ -733,28 +733,6 @@ struct InputStateTracker {
}
}
void MouseControl() {
// Disabled by default, needs a workaround to map to psp keys.
if (g_Config.bMouseControl) {
float scaleFactor_x = g_display.dpi_scale_x * 0.1 * g_Config.fMouseSensitivity;
float scaleFactor_y = g_display.dpi_scale_y * 0.1 * g_Config.fMouseSensitivity;
AxisInput axis[2];
axis[0].axisId = JOYSTICK_AXIS_MOUSE_REL_X;
axis[0].deviceId = DEVICE_ID_MOUSE;
axis[0].value = std::max(-1.0f, std::min(1.0f, mouseDeltaX * scaleFactor_x));
axis[1].axisId = JOYSTICK_AXIS_MOUSE_REL_Y;
axis[1].deviceId = DEVICE_ID_MOUSE;
axis[1].value = std::max(-1.0f, std::min(1.0f, mouseDeltaY * scaleFactor_y));
if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) {
NativeAxis(axis, 2);
}
mouseDeltaX *= g_Config.fMouseSmoothing;
mouseDeltaY *= g_Config.fMouseSmoothing;
}
}
void MouseCaptureControl() {
bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_Config.bMapMouse);
if (mouseCaptured != captureMouseCondition) {
@ -767,8 +745,6 @@ struct InputStateTracker {
}
bool mouseDown;
float mouseDeltaX;
float mouseDeltaY;
int mouseWheelMovedUpFrames;
int mouseWheelMovedDownFrames;
bool mouseCaptured;
@ -1061,8 +1037,7 @@ static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputSta
input.id = 0;
NativeTouch(input);
}
inputTracker->mouseDeltaX += event.motion.xrel;
inputTracker->mouseDeltaY += event.motion.yrel;
NativeMouseDelta(event.motion.xrel, event.motion.yrel);
break;
case SDL_MOUSEBUTTONUP:
switch (event.button.button) {
@ -1484,7 +1459,6 @@ int main(int argc, char *argv[]) {
UpdateSDLCursor();
inputTracker.MouseControl();
inputTracker.MouseCaptureControl();
@ -1514,7 +1488,6 @@ int main(int argc, char *argv[]) {
UpdateSDLCursor();
inputTracker.MouseControl();
inputTracker.MouseCaptureControl();
bool renderThreadPaused = Core_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;

View File

@ -729,18 +729,34 @@ void GameSettingsScreen::CreateControlsSettings(UI::ViewGroup *controlsSettings)
return UI::EVENT_CONTINUE;
});
controlsSettings->Add(new PopupSliderChoice(&g_Config.iRapidFireInterval, 1, 10, 5, co->T("Rapid fire interval"), screenManager(), "frames"));
#if defined(USING_WIN_UI) || defined(SDL)
controlsSettings->Add(new ItemHeader(co->T("Mouse", "Mouse settings")));
CheckBox *mouseControl = controlsSettings->Add(new CheckBox(&g_Config.bMouseControl, co->T("Use Mouse Control")));
mouseControl->OnClick.Add([=](EventParams &e) {
if (g_Config.bMouseControl)
settingInfo_->Show(co->T("MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon."), e.v);
return UI::EVENT_CONTINUE;
});
controlsSettings->Add(new CheckBox(&g_Config.bMouseConfine, co->T("Confine Mouse", "Trap mouse within window/display area")))->SetEnabledPtr(&g_Config.bMouseControl);
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSensitivity, 0.01f, 1.0f, 0.1f, co->T("Mouse sensitivity"), 0.01f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bMouseControl);
controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSmoothing, 0.0f, 0.95f, 0.9f, co->T("Mouse smoothing"), 0.05f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bMouseControl);
#if defined(USING_WIN_UI) || defined(SDL) || PPSSPP_PLATFORM(ANDROID)
bool enableMouseSettings = true;
#if PPSSPP_PLATFORM(ANDROID)
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) < 12) {
enableMouseSettings = false;
}
#endif
#else
bool enableMouseSettings = false;
#endif
if (enableMouseSettings) {
controlsSettings->Add(new ItemHeader(co->T("Mouse", "Mouse settings")));
CheckBox *mouseControl = controlsSettings->Add(new CheckBox(&g_Config.bMouseControl, co->T("Use Mouse Control")));
mouseControl->OnClick.Add([=](EventParams &e) {
if (g_Config.bMouseControl)
settingInfo_->Show(co->T("MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon."), e.v);
return UI::EVENT_CONTINUE;
});
#if !PPSSPP_PLATFORM(ANDROID)
controlsSettings->Add(new CheckBox(&g_Config.bMouseConfine, co->T("Confine Mouse", "Trap mouse within window/display area")))->SetEnabledPtr(&g_Config.bMouseControl);
#endif
auto sensitivitySlider = controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSensitivity, 0.01f, 1.0f, 0.1f, co->T("Mouse sensitivity"), 0.01f, screenManager(), "x"));
sensitivitySlider->SetEnabledPtr(&g_Config.bMouseControl);
sensitivitySlider->SetLiveUpdate(true);
auto smoothingSlider = controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSmoothing, 0.0f, 0.95f, 0.9f, co->T("Mouse smoothing"), 0.05f, screenManager(), "x"));
smoothingSlider->SetEnabledPtr(&g_Config.bMouseControl);
smoothingSlider->SetLiveUpdate(true);
}
}
}

View File

@ -1071,6 +1071,8 @@ static Matrix4x4 ComputeOrthoMatrix(float xres, float yres) {
return ortho;
}
static void SendMouseDeltaAxis();
void NativeFrame(GraphicsContext *graphicsContext) {
PROFILE_END_FRAME();
@ -1203,6 +1205,8 @@ void NativeFrame(GraphicsContext *graphicsContext) {
if (sleepTime > 0)
sleep_ms(sleepTime);
}
SendMouseDeltaAxis();
}
bool HandleGlobalMessage(UIMessage message, const std::string &value) {
@ -1358,6 +1362,60 @@ void NativeAxis(const AxisInput *axes, size_t count) {
}
}
float g_mouseDeltaX = 0;
float g_mouseDeltaY = 0;
void NativeMouseDelta(float dx, float dy) {
// Remap, shared code. Then send it as a regular axis event.
if (!g_Config.bMouseControl)
return;
// Accumulate mouse deltas, for some kind of smoothing.
g_mouseDeltaX += dx;
g_mouseDeltaY += dy;
}
// Called from NativeFrame.
static void SendMouseDeltaAxis() {
static double lastTime = 0.0f;
double now = time_now_d();
if (lastTime == 0.0) {
lastTime = now;
return;
}
double dt = now - lastTime;
lastTime = now;
float scaleFactor_x = g_display.dpi_scale_x * 0.1 * g_Config.fMouseSensitivity;
float scaleFactor_y = g_display.dpi_scale_y * 0.1 * g_Config.fMouseSensitivity;
float mx = clamp_value(g_mouseDeltaX * scaleFactor_x, -1.0f, 1.0f);
float my = clamp_value(g_mouseDeltaY * scaleFactor_y, -1.0f, 1.0f);
// Decay the mouse deltas. This is where we should use dt.
float decay = expf(-dt * 50.0f * (1.0f - g_Config.fMouseSmoothing));
g_mouseDeltaX *= decay;
g_mouseDeltaY *= decay;
AxisInput axis[2];
axis[0].axisId = JOYSTICK_AXIS_MOUSE_REL_X;
axis[0].deviceId = DEVICE_ID_MOUSE;
axis[0].value = mx;
axis[1].axisId = JOYSTICK_AXIS_MOUSE_REL_Y;
axis[1].deviceId = DEVICE_ID_MOUSE;
axis[1].value = my;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mx;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = my;
//NOTICE_LOG(SYSTEM, "delta: %0.2f %0.2f mx/my: %0.2f %0.2f dpi: %f sens: %f ",
// g_mouseDeltaX, g_mouseDeltaY, mx, my, g_display.dpi_scale_x, g_Config.fMouseSensitivity);
if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) {
NativeAxis(axis, 2);
}
}
void NativeAccelerometer(float tiltX, float tiltY, float tiltZ) {
if (g_Config.iTiltInputType == TILT_NULL) {
// if tilt events are disabled, don't do anything special.

View File

@ -317,11 +317,7 @@ namespace WindowsRawInput {
KeyInput key;
key.deviceId = DEVICE_ID_MOUSE;
float mx, my;
g_inputManager.AccumulateMouseDeltas(raw->data.mouse.lLastX, raw->data.mouse.lLastY, &mx, &my);
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mx;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = my;
NativeMouseDelta(raw->data.mouse.lLastX, raw->data.mouse.lLastY);
static const int rawInputDownID[5] = {
RI_MOUSE_LEFT_BUTTON_DOWN,

View File

@ -65,9 +65,6 @@ void UpdateConsolePosition() {
}
void WindowsInputManager::Init() {
mouseDeltaX_ = 0;
mouseDeltaY_ = 0;
//add first XInput device to respond
input.push_back(std::make_unique<XinputDevice>());
#ifndef _M_ARM
@ -102,22 +99,7 @@ void WindowsInputManager::PollControllers() {
// Disabled by default, needs a workaround to map to psp keys.
if (g_Config.bMouseControl) {
float scaleFactor_x = g_display.dpi_scale_x * 0.1 * g_Config.fMouseSensitivity;
float scaleFactor_y = g_display.dpi_scale_y * 0.1 * g_Config.fMouseSensitivity;
float mx = std::max(-1.0f, std::min(1.0f, mouseDeltaX_ * scaleFactor_x));
float my = std::max(-1.0f, std::min(1.0f, mouseDeltaY_ * scaleFactor_y));
AxisInput axis[2];
axis[0].axisId = JOYSTICK_AXIS_MOUSE_REL_X;
axis[0].deviceId = DEVICE_ID_MOUSE;
axis[0].value = mx;
axis[1].axisId = JOYSTICK_AXIS_MOUSE_REL_Y;
axis[1].deviceId = DEVICE_ID_MOUSE;
axis[1].value = my;
if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) {
NativeAxis(axis, 2);
}
NativeMouseDelta(mouseDeltaX_, mouseDeltaY_);
}
mouseDeltaX_ *= g_Config.fMouseSmoothing;

View File

@ -1278,6 +1278,13 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
return true;
}
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_mouseDelta(
JNIEnv * env, jclass, jfloat x, jfloat y) {
if (!renderer_inited)
return;
NativeMouseDelta(x, y);
}
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *, jclass, float x, float y, float z) {
if (!renderer_inited)
return;

View File

@ -1019,6 +1019,12 @@ public abstract class NativeActivity extends Activity {
}
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
if ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
float dx = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
float dy = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
NativeApp.mouseDelta(dx, dy);
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_MOVE:
// process the mouse hover movement...

View File

@ -50,6 +50,7 @@ public class NativeApp {
public static native void accelerometer(float x, float y, float z);
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);
public static native String queryConfig(String queryName);

View File

@ -15,6 +15,7 @@ import android.hardware.SensorManager;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.SurfaceView;
@ -23,6 +24,8 @@ import com.bda.controller.ControllerListener;
import com.bda.controller.KeyEvent;
import com.bda.controller.StateEvent;
import java.lang.annotation.Native;
public class NativeSurfaceView extends SurfaceView implements SensorEventListener, ControllerListener {
private static String TAG = "NativeSurfaceView";
private SensorManager mSensorManager;
@ -58,6 +61,15 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene
return ev.getToolType(pointer);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void processMouseDelta(final MotionEvent ev) {
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);
NativeApp.mouseDelta(dx, dy);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent ev) {
@ -81,9 +93,13 @@ public class NativeSurfaceView extends SurfaceView implements SensorEventListene
if (ev.getActionIndex() == i)
code = 4;
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_MOVE: {
code = 1;
if (Build.VERSION.SDK_INT >= 12) {
processMouseDelta(ev);
}
break;
}
default:
break;
}