Skip to content

Commit

Permalink
Implement full support for mouse input on Android
Browse files Browse the repository at this point in the history
The smoothing algorithm changed a bit now that I centralized that logic
in a way that can work with all backends.
  • Loading branch information
hrydgard committed Dec 10, 2023
1 parent 904ce4f commit db4993b
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 64 deletions.
1 change: 1 addition & 0 deletions Common/System/NativeApp.h
Expand Up @@ -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.
Expand Down
29 changes: 1 addition & 28 deletions SDL/SDLMain.cpp
Expand Up @@ -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) {
Expand All @@ -767,8 +745,6 @@ struct InputStateTracker {
}

bool mouseDown;
float mouseDeltaX;
float mouseDeltaY;
int mouseWheelMovedUpFrames;
int mouseWheelMovedDownFrames;
bool mouseCaptured;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1484,7 +1459,6 @@ int main(int argc, char *argv[]) {

UpdateSDLCursor();

inputTracker.MouseControl();
inputTracker.MouseCaptureControl();


Expand Down Expand Up @@ -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;
Expand Down
38 changes: 27 additions & 11 deletions UI/GameSettingsScreen.cpp
Expand Up @@ -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);
}
}
}

Expand Down
58 changes: 58 additions & 0 deletions UI/NativeApp.cpp
Expand Up @@ -1071,6 +1071,8 @@ static Matrix4x4 ComputeOrthoMatrix(float xres, float yres) {
return ortho;
}

static void SendMouseDeltaAxis();

void NativeFrame(GraphicsContext *graphicsContext) {
PROFILE_END_FRAME();

Expand Down Expand Up @@ -1203,6 +1205,8 @@ void NativeFrame(GraphicsContext *graphicsContext) {
if (sleepTime > 0)
sleep_ms(sleepTime);
}

SendMouseDeltaAxis();
}

bool HandleGlobalMessage(UIMessage message, const std::string &value) {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 1 addition & 5 deletions Windows/RawInput.cpp
Expand Up @@ -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,
Expand Down
20 changes: 1 addition & 19 deletions Windows/WindowsHost.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions android/jni/app-android.cpp
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions android/src/org/ppsspp/ppsspp/NativeActivity.java
Expand Up @@ -1019,6 +1019,12 @@ public boolean onGenericMotionEvent(MotionEvent event) {
}

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...
Expand Down
1 change: 1 addition & 0 deletions android/src/org/ppsspp/ppsspp/NativeApp.java
Expand Up @@ -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);
Expand Down
18 changes: 17 additions & 1 deletion android/src/org/ppsspp/ppsspp/NativeSurfaceView.java
Expand Up @@ -15,6 +15,7 @@
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.SurfaceView;

Expand All @@ -23,6 +24,8 @@
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;
Expand Down Expand Up @@ -58,6 +61,15 @@ private int getToolType(final MotionEvent ev, int pointer) {
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) {
Expand All @@ -81,9 +93,13 @@ public boolean onTouchEvent(final MotionEvent ev) {
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;
}
Expand Down

0 comments on commit db4993b

Please sign in to comment.