2024-05-22 08:38:40 +00:00
|
|
|
#include "Controls.h"
|
|
|
|
|
|
|
|
#include "Common/Log.h"
|
2024-05-22 10:11:51 +00:00
|
|
|
#include "Common/TimeUtil.h"
|
2024-05-22 09:18:03 +00:00
|
|
|
#include "Common/Input/InputState.h"
|
|
|
|
#include "Common/System/NativeApp.h"
|
2024-05-22 09:36:52 +00:00
|
|
|
#include "Common/System/Display.h"
|
2024-05-22 10:11:51 +00:00
|
|
|
#include "Core/KeyMap.h"
|
2024-05-22 09:18:03 +00:00
|
|
|
|
|
|
|
static void controllerButtonPressed(BOOL pressed, InputKeyCode keyCode) {
|
|
|
|
KeyInput key;
|
|
|
|
key.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
key.flags = pressed ? KEY_DOWN : KEY_UP;
|
|
|
|
key.keyCode = keyCode;
|
|
|
|
NativeKey(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SetupController(GCController *controller) {
|
|
|
|
GCGamepad *baseProfile = controller.gamepad;
|
|
|
|
if (baseProfile == nil) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
controller.controllerPausedHandler = ^(GCController *controller) {
|
|
|
|
KeyInput key;
|
|
|
|
key.flags = KEY_DOWN;
|
|
|
|
key.keyCode = NKCODE_ESCAPE;
|
|
|
|
key.deviceId = DEVICE_ID_KEYBOARD;
|
|
|
|
NativeKey(key);
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_2); // Cross
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_3); // Circle
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.buttonX.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_4); // Square
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.buttonY.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_1); // Triangle
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.leftShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_7); // LTrigger
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_8); // RTrigger
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.dpad.up.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_DPAD_UP);
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.dpad.down.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_DPAD_DOWN);
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.dpad.left.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_DPAD_LEFT);
|
|
|
|
};
|
|
|
|
|
|
|
|
baseProfile.dpad.right.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_DPAD_RIGHT);
|
|
|
|
};
|
|
|
|
|
|
|
|
GCExtendedGamepad *extendedProfile = controller.extendedGamepad;
|
|
|
|
if (extendedProfile == nil)
|
|
|
|
return; // controller doesn't support extendedGamepad profile
|
|
|
|
|
|
|
|
extendedProfile.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_9); // Select
|
|
|
|
};
|
|
|
|
|
|
|
|
extendedProfile.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_10); // Start
|
|
|
|
};
|
|
|
|
|
|
|
|
#if defined(__IPHONE_12_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_1
|
|
|
|
if ([extendedProfile respondsToSelector:@selector(leftThumbstickButton)] && extendedProfile.leftThumbstickButton != nil) {
|
|
|
|
extendedProfile.leftThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_11);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if ([extendedProfile respondsToSelector:@selector(rightThumbstickButton)] && extendedProfile.rightThumbstickButton != nil) {
|
|
|
|
extendedProfile.rightThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_12);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
|
|
|
|
if ([extendedProfile respondsToSelector:@selector(buttonOptions)] && extendedProfile.buttonOptions != nil) {
|
|
|
|
extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_13);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if ([extendedProfile respondsToSelector:@selector(buttonMenu)] && extendedProfile.buttonMenu != nil) {
|
|
|
|
extendedProfile.buttonMenu.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_14);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
|
|
|
|
if ([extendedProfile respondsToSelector:@selector(buttonHome)] && extendedProfile.buttonHome != nil) {
|
|
|
|
extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
|
|
controllerButtonPressed(pressed, NKCODE_BUTTON_15);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
|
|
|
|
AxisInput axisInput;
|
|
|
|
axisInput.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
axisInput.axisId = JOYSTICK_AXIS_X;
|
|
|
|
axisInput.value = value;
|
|
|
|
NativeAxis(&axisInput, 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
extendedProfile.leftThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
|
|
|
|
AxisInput axisInput;
|
|
|
|
axisInput.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
axisInput.axisId = JOYSTICK_AXIS_Y;
|
|
|
|
axisInput.value = -value;
|
|
|
|
NativeAxis(&axisInput, 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Map right thumbstick as another analog stick, particularly useful for controllers
|
|
|
|
// like the DualShock 3/4 when connected to an iOS device
|
|
|
|
extendedProfile.rightThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
|
|
|
|
AxisInput axisInput;
|
|
|
|
axisInput.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
axisInput.axisId = JOYSTICK_AXIS_Z;
|
|
|
|
axisInput.value = value;
|
|
|
|
NativeAxis(&axisInput, 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
extendedProfile.rightThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
|
|
|
|
AxisInput axisInput;
|
|
|
|
axisInput.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
axisInput.axisId = JOYSTICK_AXIS_RZ;
|
|
|
|
axisInput.value = -value;
|
|
|
|
NativeAxis(&axisInput, 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2024-05-22 09:36:52 +00:00
|
|
|
|
2024-05-22 09:59:26 +00:00
|
|
|
void TouchTracker::SendTouchEvent(float x, float y, int code, int pointerId) {
|
2024-05-22 09:36:52 +00:00
|
|
|
float scale = [UIScreen mainScreen].scale;
|
|
|
|
if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) {
|
|
|
|
scale = [UIScreen mainScreen].nativeScale;
|
|
|
|
}
|
|
|
|
|
|
|
|
float dp_xscale = (float)g_display.dp_xres / (float)g_display.pixel_xres;
|
|
|
|
float dp_yscale = (float)g_display.dp_yres / (float)g_display.pixel_yres;
|
|
|
|
|
|
|
|
float scaledX = (int)(x * dp_xscale) * scale;
|
|
|
|
float scaledY = (int)(y * dp_yscale) * scale;
|
|
|
|
|
|
|
|
TouchInput input;
|
|
|
|
input.x = scaledX;
|
|
|
|
input.y = scaledY;
|
|
|
|
switch (code) {
|
|
|
|
case 1: input.flags = TOUCH_DOWN; break;
|
|
|
|
case 2: input.flags = TOUCH_UP; break;
|
|
|
|
default: input.flags = TOUCH_MOVE; break;
|
|
|
|
}
|
|
|
|
input.id = pointerId;
|
|
|
|
NativeTouch(input);
|
|
|
|
}
|
|
|
|
|
2024-05-22 09:59:26 +00:00
|
|
|
int TouchTracker::ToTouchID(UITouch *uiTouch, bool allowAllocate) {
|
|
|
|
// Find the id for the touch.
|
|
|
|
for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) {
|
|
|
|
if (touches_[localId] == uiTouch) {
|
|
|
|
return localId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate a new one, perhaps?
|
|
|
|
if (allowAllocate) {
|
|
|
|
for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) {
|
|
|
|
if (touches_[localId] == 0) {
|
|
|
|
touches_[localId] = uiTouch;
|
|
|
|
return localId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// None were free. Ignore?
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TouchTracker::Began(NSSet *touches, UIView *view) {
|
|
|
|
for (UITouch* touch in touches) {
|
|
|
|
CGPoint point = [touch locationInView:view];
|
|
|
|
int touchId = ToTouchID(touch, true);
|
|
|
|
SendTouchEvent(point.x, point.y, 1, touchId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TouchTracker::Moved(NSSet *touches, UIView *view) {
|
|
|
|
for (UITouch* touch in touches) {
|
|
|
|
CGPoint point = [touch locationInView:view];
|
|
|
|
int touchId = ToTouchID(touch, true);
|
|
|
|
SendTouchEvent(point.x, point.y, 0, touchId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TouchTracker::Ended(NSSet *touches, UIView *view) {
|
|
|
|
for (UITouch* touch in touches) {
|
|
|
|
CGPoint point = [touch locationInView:view];
|
|
|
|
int touchId = ToTouchID(touch, false);
|
|
|
|
if (touchId >= 0) {
|
|
|
|
SendTouchEvent(point.x, point.y, 2, touchId);
|
|
|
|
touches_[touchId] = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TouchTracker::Cancelled(NSSet *touches, UIView *view) {
|
|
|
|
for (UITouch* touch in touches) {
|
|
|
|
CGPoint point = [touch locationInView:view];
|
|
|
|
int touchId = ToTouchID(touch, false);
|
|
|
|
if (touchId >= 0) {
|
|
|
|
SendTouchEvent(point.x, point.y, 2, touchId);
|
|
|
|
touches_[touchId] = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-05-22 10:11:51 +00:00
|
|
|
|
|
|
|
void ICadeTracker::InitKeyMap() {
|
|
|
|
iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP;
|
|
|
|
iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT;
|
|
|
|
iCadeToKeyMap[iCadeJoystickDown] = NKCODE_DPAD_DOWN;
|
|
|
|
iCadeToKeyMap[iCadeJoystickLeft] = NKCODE_DPAD_LEFT;
|
|
|
|
iCadeToKeyMap[iCadeButtonA] = NKCODE_BUTTON_9; // Select
|
|
|
|
iCadeToKeyMap[iCadeButtonB] = NKCODE_BUTTON_7; // LTrigger
|
|
|
|
iCadeToKeyMap[iCadeButtonC] = NKCODE_BUTTON_10; // Start
|
|
|
|
iCadeToKeyMap[iCadeButtonD] = NKCODE_BUTTON_8; // RTrigger
|
|
|
|
iCadeToKeyMap[iCadeButtonE] = NKCODE_BUTTON_4; // Square
|
|
|
|
iCadeToKeyMap[iCadeButtonF] = NKCODE_BUTTON_2; // Cross
|
|
|
|
iCadeToKeyMap[iCadeButtonG] = NKCODE_BUTTON_1; // Triangle
|
|
|
|
iCadeToKeyMap[iCadeButtonH] = NKCODE_BUTTON_3; // Circle
|
|
|
|
}
|
|
|
|
|
|
|
|
void ICadeTracker::ButtonDown(iCadeState button) {
|
|
|
|
if (simulateAnalog &&
|
|
|
|
((button == iCadeJoystickUp) ||
|
|
|
|
(button == iCadeJoystickDown) ||
|
|
|
|
(button == iCadeJoystickLeft) ||
|
|
|
|
(button == iCadeJoystickRight))) {
|
|
|
|
AxisInput axis;
|
|
|
|
switch (button) {
|
|
|
|
case iCadeJoystickUp :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_Y;
|
|
|
|
axis.value = -1.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickDown :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_Y;
|
|
|
|
axis.value = 1.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickLeft :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_X;
|
|
|
|
axis.value = -1.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickRight :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_X;
|
|
|
|
axis.value = 1.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
axis.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
NativeAxis(&axis, 1);
|
|
|
|
} else {
|
|
|
|
KeyInput key;
|
|
|
|
key.flags = KEY_DOWN;
|
|
|
|
key.keyCode = iCadeToKeyMap[button];
|
|
|
|
key.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
NativeKey(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ICadeTracker::ButtonUp(iCadeState button) {
|
|
|
|
if (!iCadeConnectNotified) {
|
|
|
|
iCadeConnectNotified = true;
|
|
|
|
KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0, "iCade");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (button == iCadeButtonA) {
|
|
|
|
// Pressing Select twice within 1 second toggles the DPad between
|
|
|
|
// normal operation and simulating the Analog stick.
|
|
|
|
if ((lastSelectPress + 1.0f) > time_now_d())
|
|
|
|
simulateAnalog = !simulateAnalog;
|
|
|
|
lastSelectPress = time_now_d();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (button == iCadeButtonC) {
|
|
|
|
// Pressing Start twice within 1 second will take to the Emu menu
|
|
|
|
if ((lastStartPress + 1.0f) > time_now_d()) {
|
|
|
|
KeyInput key;
|
|
|
|
key.flags = KEY_DOWN;
|
|
|
|
key.keyCode = NKCODE_ESCAPE;
|
|
|
|
key.deviceId = DEVICE_ID_KEYBOARD;
|
|
|
|
NativeKey(key);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
lastStartPress = time_now_d();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (simulateAnalog &&
|
|
|
|
((button == iCadeJoystickUp) ||
|
|
|
|
(button == iCadeJoystickDown) ||
|
|
|
|
(button == iCadeJoystickLeft) ||
|
|
|
|
(button == iCadeJoystickRight))) {
|
|
|
|
AxisInput axis;
|
|
|
|
switch (button) {
|
|
|
|
case iCadeJoystickUp :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_Y;
|
|
|
|
axis.value = 0.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickDown :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_Y;
|
|
|
|
axis.value = 0.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickLeft :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_X;
|
|
|
|
axis.value = 0.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case iCadeJoystickRight :
|
|
|
|
axis.axisId = JOYSTICK_AXIS_X;
|
|
|
|
axis.value = 0.0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
axis.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
NativeAxis(&axis, 1);
|
|
|
|
} else {
|
|
|
|
KeyInput key;
|
|
|
|
key.flags = KEY_UP;
|
|
|
|
key.keyCode = iCadeToKeyMap[button];
|
|
|
|
key.deviceId = DEVICE_ID_PAD_0;
|
|
|
|
NativeKey(key);
|
|
|
|
}
|
|
|
|
}
|