diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 924f66839..d92278ff5 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "controller.h" @@ -26,18 +26,24 @@ static const Controller::ControllerInfo* s_controller_info[] = { &GunCon::INFO, &PlayStationMouse::INFO, }; -Controller::Controller(u32 index) : m_index(index) {} +Controller::Controller(u32 index) : m_index(index) +{ +} Controller::~Controller() = default; -void Controller::Reset() {} +void Controller::Reset() +{ +} bool Controller::DoState(StateWrapper& sw, bool apply_input_state) { return !sw.HasError(); } -void Controller::ResetTransferState() {} +void Controller::ResetTransferState() +{ +} bool Controller::Transfer(const u8 data_in, u8* data_out) { @@ -50,7 +56,9 @@ float Controller::GetBindState(u32 index) const return 0.0f; } -void Controller::SetBindState(u32 index, float value) {} +void Controller::SetBindState(u32 index, float value) +{ +} u32 Controller::GetButtonStateBits() const { @@ -67,11 +75,8 @@ std::optional Controller::GetAnalogInputBytes() const return std::nullopt; } -void Controller::LoadSettings(SettingsInterface& si, const char* section) {} - -bool Controller::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) +void Controller::LoadSettings(SettingsInterface& si, const char* section) { - return false; } std::unique_ptr Controller::Create(ControllerType type, u32 index) diff --git a/src/core/controller.h b/src/core/controller.h index 7873da645..50823bde2 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -87,9 +87,6 @@ public: /// Loads/refreshes any per-controller settings. virtual void LoadSettings(SettingsInterface& si, const char* section); - /// Returns the software cursor to use for this controller, if any. - virtual bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode); - /// Creates a new controller of the specified type. static std::unique_ptr Create(ControllerType type, u32 index); diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 06550446e..44ec76160 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1010,21 +1010,30 @@ void GPU::UpdateCommandTickEvent() m_command_tick_event->SetIntervalAndSchedule(GPUTicksToSystemTicks(m_pending_command_ticks)); } -bool GPU::ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y, float x_scale, u32* out_tick, - u32* out_line) const +void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x, + float* display_y) const { - float left_padding, top_padding, scale; - CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), &left_padding, &top_padding, - &scale, nullptr); + float scale; + const Common::Rectangle draw_rc = CalculateDrawRect( + g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), nullptr, nullptr, &scale, nullptr); // convert coordinates to active display region, then to full display region - const float scaled_display_x = static_cast(window_x) - left_padding; - const float scaled_display_y = static_cast(window_y) - top_padding; + const float scaled_display_x = (window_x - draw_rc.left) / static_cast(draw_rc.GetWidth()); + const float scaled_display_y = (window_y - draw_rc.top) / static_cast(draw_rc.GetHeight()); // scale back to internal resolution - float display_x = scaled_display_x / scale / x_scale; - float display_y = scaled_display_y / scale; + *display_x = scaled_display_x * static_cast(m_crtc_state.display_width); + *display_y = scaled_display_y * static_cast(m_crtc_state.display_height); + Log_DebugPrintf("win %.0f,%.0f -> disp %.2f,%.2f (size %u,%u frac %f,%f)", window_x, window_y, *display_x, *display_y, + m_crtc_state.display_width, m_crtc_state.display_height, + *display_x / static_cast(m_crtc_state.display_width), + *display_y / static_cast(m_crtc_state.display_height)); +} + +bool GPU::ConvertDisplayCoordinatesToBeamTicksAndLines(float display_x, float display_y, float x_scale, u32* out_tick, + u32* out_line) const +{ if (x_scale != 1.0f) { const float dw = static_cast(m_crtc_state.display_width); @@ -1033,11 +1042,6 @@ bool GPU::ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y display_x = (((scaled_x + 1.0f) * 0.5f) * dw); // -1..1 -> 0..1 } - Log_DebugPrintf("win %d,%d -> disp %.2f,%.2f (size %u,%u frac %f,%f)", window_x, window_y, display_x, display_y, - m_crtc_state.display_width, m_crtc_state.display_height, - display_x / static_cast(m_crtc_state.display_width), - display_y / static_cast(m_crtc_state.display_height)); - if (display_x < 0 || static_cast(display_x) >= m_crtc_state.display_width || display_y < 0 || static_cast(display_y) >= m_crtc_state.display_height) { diff --git a/src/core/gpu.h b/src/core/gpu.h index 9180803eb..f73cbd880 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -171,8 +171,10 @@ public: static std::unique_ptr CreateSoftwareRenderer(); // Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns. - bool ConvertScreenCoordinatesToBeamTicksAndLines(s32 window_x, s32 window_y, float x_scale, u32* out_tick, - u32* out_line) const; + void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x, + float* display_y) const; + bool ConvertDisplayCoordinatesToBeamTicksAndLines(float display_x, float display_y, float x_scale, u32* out_tick, + u32* out_line) const; // Returns the video clock frequency. TickCount GetCRTCFrequency() const; diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index d80b53f51..046ce3f68 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "guncon.h" @@ -6,11 +6,13 @@ #include "host.h" #include "system.h" +#include "util/imgui_manager.h" #include "util/input_manager.h" #include "util/state_wrapper.h" #include "common/assert.h" #include "common/path.h" +#include "common/string_util.h" #include @@ -19,13 +21,21 @@ Log_SetChannel(GunCon); #endif -static constexpr std::array(GunCon::Button::Count)> s_button_indices = {{13, 3, 14}}; +static constexpr std::array(GunCon::Binding::ButtonCount)> s_button_indices = {{13, 3, 14}}; GunCon::GunCon(u32 index) : Controller(index) { } -GunCon::~GunCon() = default; +GunCon::~GunCon() +{ + if (!m_cursor_path.empty()) + { + const u32 cursor_index = GetSoftwarePointerIndex(); + if (cursor_index < InputManager::MAX_SOFTWARE_CURSORS) + ImGuiManager::ClearSoftwareCursor(cursor_index); + } +} ControllerType GunCon::GetType() const { @@ -71,12 +81,25 @@ float GunCon::GetBindState(u32 index) const void GunCon::SetBindState(u32 index, float value) { const bool pressed = (value >= 0.5f); - if (index == static_cast(Button::ShootOffscreen)) + if (index == static_cast(Binding::ShootOffscreen)) { if (m_shoot_offscreen != pressed) { m_shoot_offscreen = pressed; - SetBindState(static_cast(Button::Trigger), pressed); + SetBindState(static_cast(Binding::Trigger), pressed); + } + + return; + } + else if (index >= static_cast(Binding::ButtonCount)) + { + if (index >= static_cast(Binding::BindingCount) || !m_has_relative_binds) + return; + + if (m_relative_pos[index - static_cast(Binding::RelativeLeft)] != value) + { + m_relative_pos[index - static_cast(Binding::RelativeLeft)] = value; + UpdateSoftwarePointerPosition(); } return; @@ -181,18 +204,18 @@ bool GunCon::Transfer(const u8 data_in, u8* data_out) void GunCon::UpdatePosition() { - // get screen coordinates - const auto& [fmouse_x, fmouse_y] = InputManager::GetPointerAbsolutePosition(0); - const s32 mouse_x = static_cast(fmouse_x); - const s32 mouse_y = static_cast(fmouse_y); + float display_x, display_y; + const auto& [window_x, window_y] = + (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0); + g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y); // are we within the active display area? u32 tick, line; - if (mouse_x < 0 || mouse_y < 0 || - !g_gpu->ConvertScreenCoordinatesToBeamTicksAndLines(mouse_x, mouse_y, m_x_scale, &tick, &line) || + if (display_x < 0 || display_y < 0 || + !g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) || m_shoot_offscreen) { - Log_DebugPrintf("Lightgun out of range for window coordinates %d,%d", mouse_x, mouse_y); + Log_DebugPrintf("Lightgun out of range for window coordinates %.0f,%.0f", window_x, window_y); m_position_x = 0x01; m_position_y = 0x0A; return; @@ -202,8 +225,34 @@ void GunCon::UpdatePosition() const double divider = static_cast(g_gpu->GetCRTCFrequency()) / 8000000.0; m_position_x = static_cast(static_cast(tick) / static_cast(divider)); m_position_y = static_cast(line); - Log_DebugPrintf("Lightgun window coordinates %d,%d -> tick %u line %u 8mhz ticks %u", mouse_x, mouse_y, tick, line, - m_position_x); + Log_DebugPrintf("Lightgun window coordinates %.0f,%.0f -> tick %u line %u 8mhz ticks %u", display_x, display_y, tick, + line, m_position_x); +} + +std::pair GunCon::GetAbsolutePositionFromRelativeAxes() const +{ + const float screen_rel_x = (((m_relative_pos[1] > 0.0f) ? m_relative_pos[1] : -m_relative_pos[0]) + 1.0f) * 0.5f; + const float screen_rel_y = (((m_relative_pos[3] > 0.0f) ? m_relative_pos[3] : -m_relative_pos[2]) + 1.0f) * 0.5f; + return std::make_pair(screen_rel_x * ImGuiManager::GetWindowWidth(), screen_rel_y * ImGuiManager::GetWindowHeight()); +} + +bool GunCon::CanUseSoftwareCursor() const +{ + return (InputManager::MAX_POINTER_DEVICES + m_index) < InputManager::MAX_SOFTWARE_CURSORS; +} + +u32 GunCon::GetSoftwarePointerIndex() const +{ + return m_has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + m_index) : 0; +} + +void GunCon::UpdateSoftwarePointerPosition() +{ + if (m_cursor_path.empty() || !CanUseSoftwareCursor()) + return; + + const auto& [window_x, window_y] = GetAbsolutePositionFromRelativeAxes(); + ImGuiManager::SetSoftwareCursorPosition(GetSoftwarePointerIndex(), window_x, window_y); } std::unique_ptr GunCon::Create(u32 index) @@ -212,16 +261,25 @@ std::unique_ptr GunCon::Create(u32 index) } static const Controller::ControllerBindingInfo s_binding_info[] = { -#define BUTTON(name, display_name, button, genb) \ +#define BUTTON(name, display_name, binding, genb) \ { \ - name, display_name, static_cast(button), InputBindingInfo::Type::Button, genb \ + name, display_name, static_cast(binding), InputBindingInfo::Type::Button, genb \ + } +#define HALFAXIS(name, display_name, binding, genb) \ + { \ + name, display_name, static_cast(binding), InputBindingInfo::Type::HalfAxis, genb \ } // clang-format off - BUTTON("Trigger", TRANSLATE_NOOP("GunCon", "Trigger"), GunCon::Button::Trigger, GenericInputBinding::R2), - BUTTON("ShootOffscreen", TRANSLATE_NOOP("GunCon", "Shoot Offscreen"), GunCon::Button::ShootOffscreen, GenericInputBinding::L2), - BUTTON("A", TRANSLATE_NOOP("GunCon", "A"), GunCon::Button::A, GenericInputBinding::Cross), - BUTTON("B", TRANSLATE_NOOP("GunCon", "B"), GunCon::Button::B, GenericInputBinding::Circle), + BUTTON("Trigger", TRANSLATE_NOOP("GunCon", "Trigger"), GunCon::Binding::Trigger, GenericInputBinding::R2), + BUTTON("ShootOffscreen", TRANSLATE_NOOP("GunCon", "Shoot Offscreen"), GunCon::Binding::ShootOffscreen, GenericInputBinding::L2), + BUTTON("A", TRANSLATE_NOOP("GunCon", "A"), GunCon::Binding::A, GenericInputBinding::Cross), + BUTTON("B", TRANSLATE_NOOP("GunCon", "B"), GunCon::Binding::B, GenericInputBinding::Circle), + + HALFAXIS("RelativeLeft", TRANSLATE_NOOP("GunCon", "Relative Left"), GunCon::Binding::RelativeLeft, GenericInputBinding::Unknown), + HALFAXIS("RelativeRight", TRANSLATE_NOOP("GunCon", "Relative Right"), GunCon::Binding::RelativeRight, GenericInputBinding::Unknown), + HALFAXIS("RelativeUp", TRANSLATE_NOOP("GunCon", "Relative Up"), GunCon::Binding::RelativeUp, GenericInputBinding::Unknown), + HALFAXIS("RelativeDown", TRANSLATE_NOOP("GunCon", "Relative Down"), GunCon::Binding::RelativeDown, GenericInputBinding::Unknown), // clang-format on #undef BUTTON @@ -234,6 +292,10 @@ static const SettingInfo s_settings[] = { {SettingInfo::Type::Float, "CrosshairScale", TRANSLATE_NOOP("GunCon", "Crosshair Image Scale"), TRANSLATE_NOOP("GunCon", "Scale of crosshair image on screen."), "1.0", "0.0001", "100.0", "0.10", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::String, "CrosshairColor", TRANSLATE_NOOP("GunCon", "Cursor Color"), + TRANSLATE_NOOP("USB", "Applies a color to the chosen crosshair images, can be used for multiple players. Specify in " + "HTML/CSS format (e.g. #aabbcc)"), + "#ffffff"}, {SettingInfo::Type::Float, "XScale", TRANSLATE_NOOP("GunCon", "X Scale"), TRANSLATE_NOOP("GunCon", "Scales X coordinates relative to the center of the screen."), "1.0", "0.01", "2.0", "0.01", "%.0f%%", nullptr, 100.0f}}; @@ -251,24 +313,59 @@ void GunCon::LoadSettings(SettingsInterface& si, const char* section) { Controller::LoadSettings(si, section); - m_crosshair_image_path = si.GetStringValue(section, "CrosshairImagePath"); + m_x_scale = si.GetFloatValue(section, "XScale", 1.0f); + + std::string cursor_path = si.GetStringValue(section, "CrosshairImagePath"); + const float cursor_scale = si.GetFloatValue(section, "CrosshairScale", 1.0f); + u32 cursor_color = 0xFFFFFF; + if (std::string cursor_color_str = si.GetStringValue(section, "CrosshairColor", ""); !cursor_color_str.empty()) + { + // Strip the leading hash, if it's a CSS style colour. + const std::optional cursor_color_opt(StringUtil::FromChars( + cursor_color_str[0] == '#' ? std::string_view(cursor_color_str).substr(1) : std::string_view(cursor_color_str), + 16)); + if (cursor_color_opt.has_value()) + cursor_color = cursor_color_opt.value(); + } + #ifndef __ANDROID__ - if (m_crosshair_image_path.empty()) - m_crosshair_image_path = Path::Combine(EmuFolders::Resources, "images/crosshair.png"); + if (cursor_path.empty()) + cursor_path = Path::Combine(EmuFolders::Resources, "images/crosshair.png"); #endif - m_crosshair_image_scale = si.GetFloatValue(section, "CrosshairScale", 1.0f); + const s32 prev_pointer_index = GetSoftwarePointerIndex(); - m_x_scale = si.GetFloatValue(section, "XScale", 1.0f); -} - -bool GunCon::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) -{ - if (m_crosshair_image_path.empty()) - return false; - - *image_path = m_crosshair_image_path; - *image_scale = m_crosshair_image_scale; - *relative_mode = false; - return true; + m_has_relative_binds = (si.ContainsValue(section, "RelativeLeft") || si.ContainsValue(section, "RelativeRight") || + si.ContainsValue(section, "RelativeUp") || si.ContainsValue(section, "RelativeDown")); + + const s32 new_pointer_index = GetSoftwarePointerIndex(); + + if (prev_pointer_index != new_pointer_index || m_cursor_path != cursor_path || m_cursor_scale != cursor_scale || + m_cursor_color != cursor_color) + { + if (prev_pointer_index != new_pointer_index && + static_cast(prev_pointer_index) < InputManager::MAX_SOFTWARE_CURSORS) + { + ImGuiManager::ClearSoftwareCursor(prev_pointer_index); + } + + // Pointer changed, so need to update software cursor. + const bool had_software_cursor = m_cursor_path.empty(); + m_cursor_path = std::move(cursor_path); + m_cursor_scale = cursor_scale; + m_cursor_color = cursor_color; + if (static_cast(new_pointer_index) < InputManager::MAX_SOFTWARE_CURSORS) + { + if (!m_cursor_path.empty()) + { + ImGuiManager::SetSoftwareCursor(new_pointer_index, m_cursor_path, m_cursor_scale, m_cursor_color); + if (m_has_relative_binds) + UpdateSoftwarePointerPosition(); + } + else if (had_software_cursor) + { + ImGuiManager::ClearSoftwareCursor(new_pointer_index); + } + } + } } diff --git a/src/core/guncon.h b/src/core/guncon.h index 6fb712d01..bf1c86a12 100644 --- a/src/core/guncon.h +++ b/src/core/guncon.h @@ -10,13 +10,19 @@ class GunCon final : public Controller { public: - enum class Button : u8 + enum class Binding : u8 { Trigger = 0, A = 1, B = 2, ShootOffscreen = 3, - Count + ButtonCount = 4, + + RelativeLeft = 4, + RelativeRight = 5, + RelativeUp = 6, + RelativeDown = 7, + BindingCount = 8, }; static const Controller::ControllerInfo INFO; @@ -30,9 +36,8 @@ public: void Reset() override; bool DoState(StateWrapper& sw, bool apply_input_state) override; - + void LoadSettings(SettingsInterface& si, const char* section) override; - bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) override; float GetBindState(u32 index) const override; void SetBindState(u32 index, float value) override; @@ -41,8 +46,6 @@ public: bool Transfer(const u8 data_in, u8* data_out) override; private: - void UpdatePosition(); - enum class TransferState : u8 { Idle, @@ -56,15 +59,27 @@ private: YMSB }; - std::string m_crosshair_image_path; - float m_crosshair_image_scale = 1.0f; + void UpdatePosition(); + + // 0..1, not -1..1. + std::pair GetAbsolutePositionFromRelativeAxes() const; + bool CanUseSoftwareCursor() const; + u32 GetSoftwarePointerIndex() const; + void UpdateSoftwarePointerPosition(); + + std::string m_cursor_path; + float m_cursor_scale = 1.0f; + u32 m_cursor_color = 0xFFFFFFFFu; float m_x_scale = 1.0f; + float m_relative_pos[4] = {}; + // buttons are active low u16 m_button_state = UINT16_C(0xFFFF); u16 m_position_x = 0; u16 m_position_y = 0; bool m_shoot_offscreen = false; + bool m_has_relative_binds = false; TransferState m_transfer_state = TransferState::Idle; }; diff --git a/src/core/host.h b/src/core/host.h index c8424efa5..b7031b416 100644 --- a/src/core/host.h +++ b/src/core/host.h @@ -79,9 +79,6 @@ void ReportFormattedDebuggerMessage(const char* format, ...); /// such as compiling shaders when starting up. void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); -/// Enables "relative" mouse mode, locking the cursor position and returning relative coordinates. -void SetMouseMode(bool relative, bool hide_cursor); - /// Safely executes a function on the VM thread. void RunOnCPUThread(std::function function, bool block = false); diff --git a/src/core/playstation_mouse.cpp b/src/core/playstation_mouse.cpp index beecb7d0d..fba57ffca 100644 --- a/src/core/playstation_mouse.cpp +++ b/src/core/playstation_mouse.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "playstation_mouse.h" @@ -6,7 +6,6 @@ #include "host.h" #include "system.h" -#include "util/input_manager.h" #include "util/state_wrapper.h" #include "common/assert.h" @@ -16,13 +15,11 @@ Log_SetChannel(PlayStationMouse); -static constexpr std::array(PlayStationMouse::Button::Count)> s_button_indices = {{11, 10}}; +static constexpr std::array(PlayStationMouse::Binding::ButtonCount)> s_button_indices = { + {11, 10}}; PlayStationMouse::PlayStationMouse(u32 index) : Controller(index) { - const auto& [x, y] = InputManager::GetPointerAbsolutePosition(0); - m_last_host_position_x = static_cast(x); - m_last_host_position_y = static_cast(y); } PlayStationMouse::~PlayStationMouse() = default; @@ -43,11 +40,21 @@ bool PlayStationMouse::DoState(StateWrapper& sw, bool apply_input_state) return false; u16 button_state = m_button_state; - u8 delta_x = m_delta_x; - u8 delta_y = m_delta_y; + float delta_x = m_delta_x; + float delta_y = m_delta_y; sw.Do(&button_state); - sw.Do(&delta_x); - sw.Do(&delta_y); + if (sw.GetVersion() >= 60) + { + sw.Do(&delta_x); + sw.Do(&delta_y); + } + else + { + u8 dummy = 0; + sw.Do(&dummy); + sw.Do(&dummy); + } + if (apply_input_state) { m_button_state = button_state; @@ -70,8 +77,15 @@ float PlayStationMouse::GetBindState(u32 index) const void PlayStationMouse::SetBindState(u32 index, float value) { - if (index > s_button_indices.size()) + if (index >= s_button_indices.size()) + { + if (index == static_cast(Binding::PointerX)) + m_delta_x += value; + else if (index == static_cast(Binding::PointerY)) + m_delta_y += value; + return; + } if (value >= 0.5f) m_button_state &= ~(u16(1) << s_button_indices[index]); @@ -138,15 +152,22 @@ bool PlayStationMouse::Transfer(const u8 data_in, u8* data_out) case TransferState::DeltaX: { - UpdatePosition(); - *data_out = static_cast(m_delta_x); + const float delta_x = + std::clamp(std::floor(m_delta_x * m_sensitivity_x), static_cast(std::numeric_limits::min()), + static_cast(std::numeric_limits::max())); + m_delta_x -= delta_x / m_sensitivity_x; + *data_out = static_cast(delta_x); m_transfer_state = TransferState::DeltaY; return true; } case TransferState::DeltaY: { - *data_out = static_cast(m_delta_y); + const float delta_y = + std::clamp(std::floor(m_delta_y * m_sensitivity_y), static_cast(std::numeric_limits::min()), + static_cast(std::numeric_limits::max())); + m_delta_y -= delta_y / m_sensitivity_x; + *data_out = static_cast(delta_y); m_transfer_state = TransferState::Idle; return false; } @@ -158,22 +179,12 @@ bool PlayStationMouse::Transfer(const u8 data_in, u8* data_out) } } -void PlayStationMouse::UpdatePosition() +void PlayStationMouse::LoadSettings(SettingsInterface& si, const char* section) { - // get screen coordinates - const auto& [fmouse_x, fmouse_y] = InputManager::GetPointerAbsolutePosition(0); - const s32 mouse_x = static_cast(fmouse_x); - const s32 mouse_y = static_cast(fmouse_y); - const s32 delta_x = mouse_x - m_last_host_position_x; - const s32 delta_y = mouse_y - m_last_host_position_y; - m_last_host_position_x = mouse_x; - m_last_host_position_y = mouse_y; + Controller::LoadSettings(si, section); - if (delta_x != 0 || delta_y != 0) - Log_DevPrintf("dx=%d, dy=%d", delta_x, delta_y); - - m_delta_x = static_cast(std::clamp(delta_x, std::numeric_limits::min(), std::numeric_limits::max())); - m_delta_y = static_cast(std::clamp(delta_y, std::numeric_limits::min(), std::numeric_limits::max())); + m_sensitivity_x = si.GetFloatValue(section, "SensitivityX", 1.0f); + m_sensitivity_y = si.GetFloatValue(section, "SensitivityY", 1.0f); } std::unique_ptr PlayStationMouse::Create(u32 index) @@ -188,17 +199,20 @@ static const Controller::ControllerBindingInfo s_binding_info[] = { } // clang-format off - BUTTON("Left", TRANSLATE_NOOP("PlayStationMouse", "Left Button"), PlayStationMouse::Button::Left, GenericInputBinding::Cross), - BUTTON("Right", TRANSLATE_NOOP("PlayStationMouse", "Right Button"), PlayStationMouse::Button::Right, GenericInputBinding::Circle), + { "Pointer", TRANSLATE_NOOP("PlaystationMouse", "Pointer"), static_cast(PlayStationMouse::Binding::PointerX), InputBindingInfo::Type::Pointer, GenericInputBinding::Unknown }, + BUTTON("Left", TRANSLATE_NOOP("PlayStationMouse", "Left Button"), PlayStationMouse::Binding::Left, GenericInputBinding::Cross), + BUTTON("Right", TRANSLATE_NOOP("PlayStationMouse", "Right Button"), PlayStationMouse::Binding::Right, GenericInputBinding::Circle), // clang-format on #undef BUTTON }; - static const SettingInfo s_settings[] = { - {SettingInfo::Type::Boolean, "RelativeMouseMode", TRANSLATE_NOOP("PlayStationMouse", "Relative Mouse Mode"), - TRANSLATE_NOOP("PlayStationMouse", "Locks the mouse cursor to the window, use for FPS games."), "false", nullptr, - nullptr, nullptr, nullptr, nullptr, 0.0f}, + {SettingInfo::Type::Float, "SensitivityX", TRANSLATE_NOOP("PlayStationMouse", "Horizontal Sensitivity"), + TRANSLATE_NOOP("PlayStationMouse", "Adjusts the correspondance between physical and virtual mouse movement."), "1.0", + "0.01", "2.0", "0.01", "%.0f", nullptr, 100.0f}, + {SettingInfo::Type::Float, "SensitivityY", TRANSLATE_NOOP("PlayStationMouse", "Vertical Sensitivity"), + TRANSLATE_NOOP("PlayStationMouse", "Adjusts the correspondance between physical and virtual mouse movement."), "1.0", + "0.01", "2.0", "0.01", "%.0f", nullptr, 100.0f}, }; const Controller::ControllerInfo PlayStationMouse::INFO = {ControllerType::PlayStationMouse, @@ -208,17 +222,4 @@ const Controller::ControllerInfo PlayStationMouse::INFO = {ControllerType::PlayS countof(s_binding_info), s_settings, countof(s_settings), - Controller::VibrationCapabilities::NoVibration}; - -void PlayStationMouse::LoadSettings(SettingsInterface& si, const char* section) -{ - Controller::LoadSettings(si, section); - - m_use_relative_mode = si.GetBoolValue(section, "RelativeMouseMode", false); -} - -bool PlayStationMouse::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) -{ - *relative_mode = m_use_relative_mode; - return m_use_relative_mode; -} + Controller::VibrationCapabilities::NoVibration}; \ No newline at end of file diff --git a/src/core/playstation_mouse.h b/src/core/playstation_mouse.h index 11fce1939..3c7e6b117 100644 --- a/src/core/playstation_mouse.h +++ b/src/core/playstation_mouse.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -10,11 +10,15 @@ class PlayStationMouse final : public Controller { public: - enum class Button : u8 + enum class Binding : u8 { Left = 0, Right = 1, - Count + ButtonCount = 2, + + PointerX = 2, + PointerY = 3, + BindingCount = 4, }; static const Controller::ControllerInfo INFO; @@ -36,11 +40,8 @@ public: bool Transfer(const u8 data_in, u8* data_out) override; void LoadSettings(SettingsInterface& si, const char* section) override; - bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) override; private: - void UpdatePosition(); - enum class TransferState : u8 { Idle, @@ -52,15 +53,13 @@ private: DeltaY }; - s32 m_last_host_position_x = 0; - s32 m_last_host_position_y = 0; + float m_sensitivity_x = 1.0f; + float m_sensitivity_y = 1.0f; // buttons are active low u16 m_button_state = UINT16_C(0xFFFF); - s8 m_delta_x = 0; - s8 m_delta_y = 0; + float m_delta_x = 0; + float m_delta_y = 0; TransferState m_transfer_state = TransferState::Idle; - - bool m_use_relative_mode = false; }; diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index 86d23007e..165900836 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -5,7 +5,7 @@ #include "types.h" static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; -static constexpr u32 SAVE_STATE_VERSION = 59; +static constexpr u32 SAVE_STATE_VERSION = 60; static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42; static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION); diff --git a/src/core/system.cpp b/src/core/system.cpp index abe93b1a2..ac3d10762 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1398,7 +1398,6 @@ bool System::BootSystem(SystemBootParameters parameters) // Good to go. s_state = State::Running; - UpdateSoftwareCursor(); SPU::GetOutputStream()->SetPaused(false); FullscreenUI::OnSystemStarted(); @@ -1632,7 +1631,6 @@ void System::DestroySystem() if (s_keep_gpu_device_on_shutdown && g_gpu_device) { g_gpu_device->SetDisplayMaxFPS(0.0f); - UpdateSoftwareCursor(); } else { @@ -3673,18 +3671,14 @@ void System::CheckForSettingsChanges(const Settings& old_settings) { UpdateControllers(); ResetControllers(); - UpdateSoftwareCursor(); controllers_updated = true; } } - - if (IsValid() && !controllers_updated) - { - UpdateControllerSettings(); - UpdateSoftwareCursor(); - } } + if (IsValid() && !controllers_updated) + UpdateControllerSettings(); + if (g_settings.multitap_mode != old_settings.multitap_mode) UpdateMultitaps(); @@ -4562,38 +4556,6 @@ void System::ToggleSoftwareRendering() ResetPerformanceCounters(); } -void System::UpdateSoftwareCursor() -{ - if (!IsValid()) - { - Host::SetMouseMode(false, false); - ImGuiManager::ClearSoftwareCursor(0); - return; - } - - std::string image_path; - float image_scale = 1.0f; - bool relative_mode = false; - bool hide_cursor = false; - - for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) - { - Controller* controller = System::GetController(i); - if (controller && controller->GetSoftwareCursor(&image_path, &image_scale, &relative_mode)) - { - hide_cursor = true; - break; - } - } - - Host::SetMouseMode(relative_mode, hide_cursor); - - if (!image_path.empty()) - ImGuiManager::SetSoftwareCursor(0, std::move(image_path), image_scale); - else - ImGuiManager::ClearSoftwareCursor(0); -} - void System::RequestDisplaySize(float scale /*= 0.0f*/) { if (!IsValid()) @@ -4638,7 +4600,9 @@ bool System::PresentDisplay(bool allow_skip_present) FullscreenUI::Render(); ImGuiManager::RenderTextOverlays(); ImGuiManager::RenderOSDMessages(); - ImGuiManager::RenderSoftwareCursors(); + + if (s_state == State::Running) + ImGuiManager::RenderSoftwareCursors(); } // Debug windows are always rendered, otherwise mouse input breaks on skip. diff --git a/src/core/system.h b/src/core/system.h index 6912dfd90..8f592fa7a 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -456,9 +456,6 @@ bool ShouldUseVSync(); /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); -/// Updates software cursor state, based on controllers. -void UpdateSoftwareCursor(); - /// Resizes the render window to the display size, with an optional scale. /// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x. void RequestDisplaySize(float scale = 0.0f); diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index 43faa34b6..16318901a 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -773,16 +773,7 @@ void Host::OnAchievementsHardcoreModeChanged(bool enabled) void Host::SetMouseMode(bool relative, bool hide_cursor) { -#if 0 - // TODO: Find a better home for this. - if (InputManager::HasPointerAxisBinds()) - { - relative = true; - hide_cursor = true; - } - - // emit g_emu_thread->mouseModeRequested(relative, hide_cursor); -#endif + // noop } void Host::PumpMessagesOnCPUThread() diff --git a/src/duckstation-qt/controllerbindingwidget_guncon.ui b/src/duckstation-qt/controllerbindingwidget_guncon.ui index 2c4b6af24..ac9c3f464 100644 --- a/src/duckstation-qt/controllerbindingwidget_guncon.ui +++ b/src/duckstation-qt/controllerbindingwidget_guncon.ui @@ -6,8 +6,8 @@ 0 0 - 1100 - 504 + 1010 + 418 @@ -18,29 +18,377 @@ - 1100 - 500 + 1000 + 400 Form - - - 0 - - - 0 - - - 0 - - - 0 - - - - + + + + + Trigger + + + + + + Fire Offscreen + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + Fire + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Side Buttons + + + + + + B + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + A + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + PushButton + + + + + + + + + + + + + Relative Aiming + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + Qt::Vertical @@ -53,97 +401,8 @@ - - - - Side Buttons - - - - - - B - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - PushButton - - - - - - - - - - A - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - PushButton - - - - - - - - - - - + + Qt::Vertical @@ -155,11 +414,7 @@ - - - - - + Qt::Horizontal @@ -172,8 +427,8 @@ - - + + 0 @@ -197,7 +452,7 @@ - + Qt::Horizontal @@ -212,157 +467,29 @@ - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Trigger - - - - - - Fire Offscreen - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - PushButton - - - - - - - - - - Fire - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - PushButton - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Vertical + + + + Pointer Setup - - - 20 - 40 - - - + + + + + <p>By default, GunCon will use the mouse pointer. To use the mouse, you <strong>do not</strong> need to configure any bindings apart from the trigger and buttons.</p> + +<p>If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be <strong>left unbound</strong>.</p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index ee57d7029..c9ea8e5bd 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "controllerbindingwidgets.h" @@ -597,7 +597,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge } SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, sb, section, std::move(key_name), si.multiplier, - si.FloatDefaultValue() * multiplier); + si.FloatDefaultValue()); } else { diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index f5103ea0d..4774bd112 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -515,6 +515,7 @@ void MainWindow::onSystemStarted() updateEmulationActions(false, true, Achievements::IsHardcoreModeActive()); updateWindowTitle(); updateStatusBarWidgetVisibility(); + updateDisplayWidgetCursor(); } void MainWindow::onSystemPaused() diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 3e8d230e2..4f9651881 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1566,13 +1566,6 @@ void Host::OnGameChanged(const std::string& disc_path, const std::string& game_s void Host::SetMouseMode(bool relative, bool hide_cursor) { - // TODO: Find a better home for this. - if (InputManager::HasPointerAxisBinds()) - { - relative = true; - hide_cursor = true; - } - emit g_emu_thread->mouseModeRequested(relative, hide_cursor); } diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 086c2f1f9..9180bdfd0 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -71,6 +71,8 @@ static std::vector s_standard_font_data; static std::vector s_fixed_font_data; static std::vector s_icon_font_data; +static float s_window_width; +static float s_window_height; static Common::Timer s_last_render_time; // cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events @@ -160,9 +162,10 @@ bool ImGuiManager::Initialize(float global_scale, bool show_osd_messages) io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; #endif + s_window_width = static_cast(g_gpu_device->GetWindowWidth()); + s_window_height = static_cast(g_gpu_device->GetWindowHeight()); io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling - io.DisplaySize.x = static_cast(g_gpu_device->GetWindowWidth()); - io.DisplaySize.y = static_cast(g_gpu_device->GetWindowHeight()); + io.DisplaySize = ImVec2(s_window_width, s_window_height); SetKeyMap(); SetStyle(); @@ -197,12 +200,24 @@ void ImGuiManager::Shutdown() ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr); } +float ImGuiManager::GetWindowWidth() +{ + return s_window_width; +} + +float ImGuiManager::GetWindowHeight() +{ + return s_window_height; +} + void ImGuiManager::WindowResized() { const u32 new_width = g_gpu_device ? g_gpu_device->GetWindowWidth() : 0; const u32 new_height = g_gpu_device ? g_gpu_device->GetWindowHeight() : 0; - ImGui::GetIO().DisplaySize = ImVec2(static_cast(new_width), static_cast(new_height)); + s_window_width = static_cast(new_width); + s_window_height = static_cast(new_height); + ImGui::GetIO().DisplaySize = ImVec2(s_window_width, s_window_height); // restart imgui frame on the new window size to pick it up, otherwise we draw to the old size ImGui::EndFrame(); @@ -727,7 +742,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) const float margin = std::ceil(10.0f * scale); const float padding = std::ceil(8.0f * scale); const float rounding = std::ceil(5.0f * scale); - const float max_width = ImGui::GetIO().DisplaySize.x - (margin + padding) * 2.0f; + const float max_width = s_window_width - (margin + padding) * 2.0f; float position_x = margin; float position_y = margin; @@ -938,7 +953,7 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value void ImGuiManager::CreateSoftwareCursorTextures() { - for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) + for (u32 i = 0; i < static_cast(s_software_cursors.size()); i++) { if (!s_software_cursors[i].image_path.empty()) UpdateSoftwareCursorTexture(i); @@ -947,10 +962,8 @@ void ImGuiManager::CreateSoftwareCursorTextures() void ImGuiManager::DestroySoftwareCursorTextures() { - for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) - { - s_software_cursors[i].texture.reset(); - } + for (SoftwareCursor& sc : s_software_cursors) + sc.texture.reset(); } void ImGuiManager::UpdateSoftwareCursorTexture(u32 index) diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index b3c137ef2..29e2930ed 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -30,6 +30,10 @@ bool Initialize(float global_scale, bool show_osd_messages); /// Frees all ImGui resources. void Shutdown(); +/// Returns the size of the display window. Can be safely called from any thread. +float GetWindowWidth(); +float GetWindowHeight(); + /// Updates internal state when the window is size. void WindowResized(); diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index d78454875..8df88ec63 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -174,6 +174,9 @@ static std::array(InputPointerAxis: s_pointer_state; static std::array(InputPointerAxis::Count)> s_pointer_axis_scale; +using PointerMoveCallback = std::function; +static std::vector> s_pointer_move_callbacks; + // ------------------------------------------------------------------------ // Binding Parsing // ------------------------------------------------------------------------ @@ -699,16 +702,60 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect { const Controller::ControllerBindingInfo& bi = cinfo->bindings[i]; const std::vector bindings(si.GetStringList(section.c_str(), bi.name)); - if (!bindings.empty()) - { - AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) { - if (!System::IsValid()) - return; - Controller* c = System::GetController(pad_index); - if (c) - c->SetBindState(bind_index, value); - }}); + switch (bi.type) + { + case InputBindingInfo::Type::Button: + case InputBindingInfo::Type::HalfAxis: + case InputBindingInfo::Type::Axis: + { + if (!bindings.empty()) + { + AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) { + if (!System::IsValid()) + return; + + Controller* c = System::GetController(pad_index); + if (c) + c->SetBindState(bind_index, value); + }}); + } + } + break; + + case InputBindingInfo::Type::Pointer: + { + auto cb = [pad_index, base = bi.bind_index](InputBindingKey key, float value) { + if (!System::IsValid()) + return; + + Controller* c = System::GetController(pad_index); + if (c) + c->SetBindState(base + key.data, value); + }; + + // bind pointer 0 by default + if (bindings.empty()) + { + s_pointer_move_callbacks.emplace_back(0, std::move(cb)); + } + else + { + for (const std::string& binding : bindings) + { + const std::optional key(GetIndexFromPointerBinding(binding)); + if (!key.has_value()) + continue; + + s_pointer_move_callbacks.emplace_back(0, cb); + } + } + } + break; + + default: + Log_ErrorPrintf("Unhandled binding info type %u", static_cast(bi.type)); + break; } } @@ -996,6 +1043,8 @@ bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInpu void InputManager::GenerateRelativeMouseEvents() { + const bool system_running = System::IsRunning(); + for (u32 device = 0; device < MAX_POINTER_DEVICES; device++) { for (u32 axis = 0; axis < static_cast(static_cast(InputPointerAxis::Count)); axis++) @@ -1011,12 +1060,24 @@ void InputManager::GenerateRelativeMouseEvents() continue; } + if (!system_running) + continue; + const float value = std::clamp(unclamped_value, -1.0f, 1.0f); if (value != state.last_value) { state.last_value = value; InvokeEvents(key, value, GenericInputBinding::Unknown); } + + if (delta != 0.0f) + { + for (const std::pair& pmc : s_pointer_move_callbacks) + { + if (pmc.first == device) + pmc.second(key, delta); + } + } } } } @@ -1062,7 +1123,24 @@ void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, void InputManager::UpdateHostMouseMode() { - // TODO: Move from System to here. + // Check for relative mode bindings, and enable if there's anything using it. + bool has_relative_mode_bindings = !s_pointer_move_callbacks.empty(); + if (!has_relative_mode_bindings) + { + for (const auto& it : s_binding_map) + { + const InputBindingKey& key = it.first; + if (key.source_type == InputSourceType::Pointer && key.source_subtype == InputSubclass::PointerAxis && + key.data >= static_cast(InputPointerAxis::X) && key.data <= static_cast(InputPointerAxis::Y)) + { + has_relative_mode_bindings = true; + break; + } + } + } + + const bool has_software_cursor = ImGuiManager::HasSoftwareCursor(0); + Host::SetMouseMode(has_relative_mode_bindings, has_relative_mode_bindings || has_software_cursor); } bool InputManager::IsUsingRawInput() @@ -1074,22 +1152,6 @@ bool InputManager::IsUsingRawInput() #endif } -bool InputManager::HasPointerAxisBinds() -{ - std::unique_lock lock(s_binding_map_write_lock); - for (const auto& it : s_binding_map) - { - const InputBindingKey& key = it.first; - if (key.source_type == InputSourceType::Pointer && key.source_subtype == InputSubclass::PointerAxis && - key.data >= static_cast(InputPointerAxis::X) && key.data <= static_cast(InputPointerAxis::Y)) - { - return true; - } - } - - return false; -} - void InputManager::SetDefaultSourceConfig(SettingsInterface& si) { si.ClearSection("InputSources"); @@ -1546,6 +1608,7 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind s_binding_map.clear(); s_pad_vibration_array.clear(); + s_pointer_move_callbacks.clear(); // Hotkeys use the base configuration, except if the custom hotkeys option is enabled. const bool use_profile_hotkeys = si.GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false); @@ -1573,6 +1636,8 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind default_scale), 1.0f); } + + UpdateHostMouseMode(); } bool InputManager::MigrateBindings(SettingsInterface& si) diff --git a/src/util/input_manager.h b/src/util/input_manager.h index b57023051..cebe41c89 100644 --- a/src/util/input_manager.h +++ b/src/util/input_manager.h @@ -326,9 +326,6 @@ void SetMacroButtonState(u32 pad, u32 index, bool state); /// Returns true if the raw input source is being used. bool IsUsingRawInput(); -/// Returns true if any bindings are present which require relative mouse movement. -bool HasPointerAxisBinds(); - /// Restores default configuration. void SetDefaultSourceConfig(SettingsInterface& si); @@ -359,4 +356,7 @@ void OnInputDeviceConnected(const std::string_view& identifier, const std::strin /// Called when an input device is disconnected. void OnInputDeviceDisconnected(const std::string_view& identifier); + +/// Enables "relative" mouse mode, locking the cursor position and returning relative coordinates. +void SetMouseMode(bool relative, bool hide_cursor); } // namespace Host