Add support for mouse input as cursor position relative to the rendering window

This commit is contained in:
ergo720 2021-02-10 20:06:12 +01:00
parent bf6d8b804f
commit 240dc03dd5
15 changed files with 190 additions and 19 deletions

View File

@ -947,12 +947,9 @@ void Settings::RemoveLegacyConfigs(unsigned int CurrentRevision)
std::string current_section = std::string(section_input_port) + std::to_string(port_num);
std::string device_name = m_si.GetValue(current_section.c_str(), sect_input_port.device, "");
// NOTE: with C++20, this can be simplified by simply calling device_name.ends_with()
if (device_name.length() >= kb_str.length()) {
if (device_name.compare(device_name.length() - kb_str.length(), kb_str.length(), kb_str) == 0) {
device_name += "Mouse";
m_si.SetValue(current_section.c_str(), sect_input_port.device, device_name.c_str(), nullptr, true);
}
if (StrEndsWith(device_name, kb_str)) {
device_name += "Mouse";
m_si.SetValue(current_section.c_str(), sect_input_port.device, device_name.c_str(), nullptr, true);
}
}

View File

@ -63,10 +63,16 @@ LRESULT CALLBACK ButtonDukeSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPA
case WM_RBUTTONDOWN: {
Button *button = reinterpret_cast<Button *>(dwRefData);
button->ClearText();
static_cast<DukeInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_CLEAR);
if (button->GetId() == IDC_SET_MOTOR) {
static_cast<DukeInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), RUMBLE_CLEAR);
if (wParam & MK_SHIFT) {
static_cast<DukeInputWindow *>(button->GetWnd())->SwapMoCursorAxis(button);
static_cast<DukeInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_SWAP);
}
else if (!(wParam & ~MK_RBUTTON)) {
button->ClearText();
static_cast<DukeInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_CLEAR);
if (button->GetId() == IDC_SET_MOTOR) {
static_cast<DukeInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), RUMBLE_CLEAR);
}
}
}
break;
@ -88,8 +94,14 @@ LRESULT CALLBACK ButtonSbcSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
case WM_RBUTTONDOWN: {
Button *button = reinterpret_cast<Button *>(dwRefData);
button->ClearText();
static_cast<SbcInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_CLEAR);
if (wParam & MK_SHIFT) {
static_cast<SbcInputWindow *>(button->GetWnd())->SwapMoCursorAxis(button);
static_cast<SbcInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_SWAP);
}
else if (!(wParam & ~MK_RBUTTON)) {
button->ClearText();
static_cast<SbcInputWindow *>(button->GetWnd())->UpdateProfile(std::string(), BUTTON_CLEAR);
}
}
break;

View File

@ -137,6 +137,12 @@ namespace DInput
AddInput(new Axis(i, ax, (2 == i) ? mo_wheel_range_neg : mo_axis_range_neg));
AddInput(new Axis(i, ax, (2 == i) ? mo_wheel_range_pos : mo_axis_range_pos));
}
// mouse cursor
AddInput(new Cursor(0, m_state_in.cursor.x, -1.0));
AddInput(new Cursor(0, m_state_in.cursor.x, 1.0));
AddInput(new Cursor(1, m_state_in.cursor.y, -1.0));
AddInput(new Cursor(1, m_state_in.cursor.y, 1.0));
}
KeyboardMouse::~KeyboardMouse()
@ -178,6 +184,7 @@ namespace DInput
}
std::memcpy(m_state_in.mouse.rgbButtons, tmp_mouse.rgbButtons, sizeof(m_state_in.mouse.rgbButtons));
UpdateCursorPosition();
return true;
}
@ -185,6 +192,21 @@ namespace DInput
return false;
}
void KeyboardMouse::UpdateCursorPosition()
{
POINT point = {};
GetCursorPos(&point);
ScreenToClient(m_hwnd, &point);
RECT rect;
GetClientRect(m_hwnd, &rect);
const auto width = std::max(rect.right - rect.left, 1l);
const auto height = std::max(rect.bottom - rect.top, 1l);
// Convert the window client coordinates to normalized device coordinates
m_state_in.cursor.x = ((2 * static_cast<ControlState>(point.x)) / width) - 1;
m_state_in.cursor.y = ((-2 * static_cast<ControlState>(point.y)) / height) + 1;
}
std::string KeyboardMouse::GetDeviceName() const
{
return "KeyboardMouse";
@ -213,6 +235,14 @@ namespace DInput
return tmpstr;
}
std::string KeyboardMouse::Cursor::GetName() const
{
static char tmpstr[] = "Cursor ..";
tmpstr[7] = (char)('X' + m_index);
tmpstr[8] = (m_range == 1.0 ? '+' : '-');
return tmpstr;
}
ControlState KeyboardMouse::Key::GetState() const
{
return (m_key != 0);
@ -227,4 +257,9 @@ namespace DInput
{
return std::clamp(ControlState(m_axis) / m_range, 0.0, 1.0);
}
ControlState KeyboardMouse::Cursor::GetState() const
{
return std::max(0.0, m_cursor / m_range);
}
}

View File

@ -51,6 +51,7 @@ namespace DInput
std::string GetDeviceName() const override;
std::string GetAPI() const override;
bool UpdateInput() override;
void SetHwnd(HWND hwnd) { m_hwnd = hwnd; }
private:
@ -78,6 +79,7 @@ namespace DInput
const uint8_t m_index;
};
// gives mouse input based on relative motion
class Axis : public Input
{
public:
@ -91,14 +93,37 @@ namespace DInput
const uint8_t m_index;
};
// gives mouse input based on cursor position relative to the rendering window
class Cursor : public Input
{
public:
Cursor(uint8_t index, const ControlState &cursor, const ControlState range)
: m_cursor(cursor), m_index(index), m_range(range) {}
std::string GetName() const override;
ControlState GetState() const override;
bool IsDetectable() const override { return false; }
private:
const ControlState &m_cursor;
const ControlState m_range;
const uint8_t m_index;
};
struct State
{
BYTE keyboard[256];
DIMOUSESTATE2 mouse;
struct
{
ControlState x, y;
} cursor;
};
void UpdateCursorPosition();
const LPDIRECTINPUTDEVICE8 m_kb_device;
const LPDIRECTINPUTDEVICE8 m_mo_device;
State m_state_in;
HWND m_hwnd;
};
}

View File

@ -93,6 +93,7 @@ public:
{
public:
virtual ControlState GetState() const = 0;
virtual bool IsDetectable() const { return true; };
};
class Output : public IoControl

View File

@ -71,11 +71,12 @@ extern CXBX_CONTROLLER_HOST_BRIDGE g_XboxControllerHostBridge[4]; // hle xinput
InputDeviceManager g_InputDeviceManager;
void InputDeviceManager::Initialize(bool is_gui)
void InputDeviceManager::Initialize(bool is_gui, HWND hwnd)
{
// Sdl::Init must be called last since it blocks when it succeeds
std::unique_lock<std::mutex> lck(m_Mtx);
m_bPendingShutdown = false;
m_hwnd = hwnd;
m_PollingThread = std::thread([this, is_gui]() {
XInput::Init(m_Mtx);
@ -623,6 +624,12 @@ void InputDeviceManager::RefreshDevices()
m_Cv.wait(lck, []() {
return Sdl::SdlPopulateOK;
});
for (auto &dev : m_Devices) {
if (StrStartsWith(dev->GetDeviceName(), "KeyboardMouse")) {
static_cast<DInput::KeyboardMouse *>(dev.get())->SetHwnd(m_hwnd);
break;
}
}
}
std::vector<std::string> InputDeviceManager::GetDeviceList() const

View File

@ -123,7 +123,7 @@ CXBX_CONTROLLER_HOST_BRIDGE, *PCXBX_CONTROLLER_HOST_BRIDGE;
class InputDeviceManager
{
public:
void Initialize(bool is_gui);
void Initialize(bool is_gui, HWND hwnd);
void Shutdown();
// read/write the input/output from/to the device attached to the supplied xbox port
bool UpdateXboxPortInput(int usb_port, void* Buffer, int Direction, int xid_type);
@ -169,6 +169,8 @@ private:
std::thread m_PollingThread;
// used to indicate that the manager is shutting down
bool m_bPendingShutdown;
// handle of the rendering or the input gui window
HWND m_hwnd;
};
extern InputDeviceManager g_InputDeviceManager;

View File

@ -121,7 +121,7 @@ InputDevice::Input* InputWindow::DetectInput(InputDevice* const Device, int ms)
Device->UpdateInput();
std::vector<bool>::iterator state = initial_states.begin();
for (; i != e && s == e; i++, state++) {
if ((*i)->GetState() > INPUT_DETECT_THRESHOLD) {
if ((*i)->IsDetectable() && ((*i)->GetState() > INPUT_DETECT_THRESHOLD)) {
if (*state == false) {
// input was not initially pressed or it was but released afterwards
s = i;
@ -290,3 +290,65 @@ void InputWindow::UpdateCurrentDevice()
m_host_dev = device_name;
EnableDefaultButton();
}
void InputWindow::SwapMoCursorAxis(Button *button)
{
// Axis X+ <-> Cursor X+
// Axis X- <-> Cursor X-
// Axis Y+ <-> Cursor Y-
// Axis Y- <-> Cursor Y+
if (StrEndsWith(m_host_dev, "KeyboardMouse")) {
assert(button != nullptr);
char control_name[HOST_BUTTON_NAME_LENGTH];
button->GetText(control_name, sizeof(control_name));
if (StrStartsWith(control_name, "Axis")) {
switch (control_name[5])
{
case 'X':
if (control_name[6] == '+') {
button->UpdateText("Cursor X+");
}
else {
button->UpdateText("Cursor X-");
}
break;
case 'Y':
if (control_name[6] == '+') {
button->UpdateText("Cursor Y-");
}
else {
button->UpdateText("Cursor Y+");
}
break;
}
return;
}
if (StrStartsWith(control_name, "Cursor")) {
switch (control_name[7])
{
case 'X':
if (control_name[8] == '+') {
button->UpdateText("Axis X+");
}
else {
button->UpdateText("Axis X-");
}
break;
case 'Y':
if (control_name[8] == '+') {
button->UpdateText("Axis Y-");
}
else {
button->UpdateText("Axis Y+");
}
break;
}
}
}
}

View File

@ -39,6 +39,7 @@
#define RUMBLE_TEST 6
#define RUMBLE_CLEAR 7
#define BUTTON_CLEAR 8
#define BUTTON_SWAP 9
#define XINPUT_DEFAULT 0
#define DINPUT_DEFAULT 1
@ -60,6 +61,7 @@ public:
virtual void UpdateProfile(const std::string& name, int command) = 0;
void UpdateCurrentDevice();
bool IsProfileSaved();
void SwapMoCursorAxis(Button *button);
protected:

View File

@ -280,3 +280,27 @@ std::string StripQuotes(const std::string& str)
{
return StripChars(str, "\"");
}
// NOTE: with C++20, this can be replaced by simply calling full_str.ends_with()
bool StrEndsWith(const std::string &full_str, const std::string &substr)
{
if (full_str.length() >= substr.length()) {
if (full_str.compare(full_str.length() - substr.length(), substr.length(), substr) == 0) {
return true;
}
}
return false;
}
// NOTE: with C++20, this can be replaced by simply calling full_str.starts_with()
bool StrStartsWith(const std::string &full_str, const std::string &substr)
{
if (!full_str.empty()) {
if (full_str.rfind(substr, 0) == 0) {
return true;
}
}
return false;
}

View File

@ -67,6 +67,8 @@ bool Memory_RW(void* Addr, void* Buf, size_t Num, bool bIsWrite);
void unix2dos(std::string& string);
std::string StripSpaces(const std::string& str);
std::string StripQuotes(const std::string& str);
bool StrEndsWith(const std::string &full_str, const std::string &substr);
bool StrStartsWith(const std::string &full_str, const std::string &substr);
// Retrieves the underlying integer value of a scoped enumerator. It allows to avoid using static_cast every time
template <typename E>

View File

@ -1497,7 +1497,7 @@ __declspec(noreturn) void CxbxKrnlInit
// Read Xbox video mode from the SMC, store it in HalBootSMCVideoMode
xbox::HalReadSMBusValue(SMBUS_ADDRESS_SYSTEM_MICRO_CONTROLLER, SMC_COMMAND_AV_PACK, FALSE, (xbox::PULONG)&xbox::HalBootSMCVideoMode);
g_InputDeviceManager.Initialize(false);
g_InputDeviceManager.Initialize(false, g_hEmuWindow);
// Now the hardware devices exist, couple the EEPROM buffer to it's device
g_EEPROM->SetEEPROM((uint8_t*)EEPROM);

View File

@ -98,7 +98,7 @@ void UpdateInputOpt(HWND hwnd)
void ShowInputConfig(HWND hwnd, HWND ChildWnd)
{
g_InputDeviceManager.Initialize(true);
g_InputDeviceManager.Initialize(true, hwnd);
g_ChildWnd = ChildWnd;
// Show dialog box

View File

@ -196,7 +196,8 @@ void DukeInputWindow::UpdateProfile(const std::string &name, int command)
}
break;
case BUTTON_CLEAR: {
case BUTTON_CLEAR:
case BUTTON_SWAP: {
m_bHasChanges = true;
}
break;

View File

@ -100,7 +100,8 @@ void SbcInputWindow::UpdateProfile(const std::string &name, int command)
}
break;
case BUTTON_CLEAR: {
case BUTTON_CLEAR:
case BUTTON_SWAP: {
m_bHasChanges = true;
}
break;