diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index 92ff5f747b..c6f84df01c 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -90,6 +90,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 @@ -196,12 +198,24 @@ void ImGuiManager::Shutdown(bool clear_state) UnloadFontData(); } +float ImGuiManager::GetWindowWidth() +{ + return s_window_width; +} + +float ImGuiManager::GetWindowHeight() +{ + return s_window_height; +} + void ImGuiManager::WindowResized() { const u32 new_width = g_gs_device ? g_gs_device->GetWindowWidth() : 0; const u32 new_height = g_gs_device ? g_gs_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); UpdateScale(); diff --git a/pcsx2/ImGui/ImGuiManager.h b/pcsx2/ImGui/ImGuiManager.h index da7c3a26e8..c7da7bb416 100644 --- a/pcsx2/ImGui/ImGuiManager.h +++ b/pcsx2/ImGui/ImGuiManager.h @@ -41,6 +41,10 @@ namespace ImGuiManager /// Frees all ImGui resources. void Shutdown(bool clear_state); + /// 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/pcsx2/USB/usb-lightgun/guncon2.cpp b/pcsx2/USB/usb-lightgun/guncon2.cpp index 61f2e281ca..d026fb302a 100644 --- a/pcsx2/USB/usb-lightgun/guncon2.cpp +++ b/pcsx2/USB/usb-lightgun/guncon2.cpp @@ -17,6 +17,7 @@ #include "GS/GS.h" #include "Host.h" +#include "ImGui/ImGuiManager.h" #include "Input/InputManager.h" #include "StateWrapper.h" #include "USB/USB.h" @@ -26,6 +27,8 @@ #include "USB/usb-lightgun/guncon2.h" #include "VMManager.h" +#include "common/StringUtil.h" + #include namespace usb_lightgun @@ -52,6 +55,10 @@ namespace usb_lightgun BID_START = 15, BID_SHOOT_OFFSCREEN = 16, BID_RECALIBRATE = 17, + BID_RELATIVE_LEFT = 18, + BID_RELATIVE_RIGHT = 19, + BID_RELATIVE_UP = 20, + BID_RELATIVE_DOWN = 21, }; // Right pain in the arse. Different games seem to have different scales.. @@ -133,6 +140,7 @@ namespace usb_lightgun ////////////////////////////////////////////////////////////////////////// // Configuration ////////////////////////////////////////////////////////////////////////// + bool has_relative_binds = false; bool custom_config = false; u32 screen_width = 640; u32 screen_height = 240; @@ -145,6 +153,10 @@ namespace usb_lightgun // Host State (Not Saved) ////////////////////////////////////////////////////////////////////////// u32 button_state = 0; + std::string cursor_path; + float cursor_scale = 1.0f; + u32 cursor_color = 0xFFFFFFFF; + float relative_pos[4] = {}; ////////////////////////////////////////////////////////////////////////// // Device State (Saved) @@ -162,6 +174,11 @@ namespace usb_lightgun void AutoConfigure(); std::tuple CalculatePosition(); + + // 0..1, not -1..1. + std::pair GetAbsolutePositionFromRelativeAxes() const; + u32 GetSoftwarePointerIndex() const; + void UpdateSoftwarePointerPosition(); }; static const USBDescStrings desc_strings = { @@ -323,6 +340,10 @@ namespace usb_lightgun static void usb_hid_unrealize(USBDevice* dev) { GunCon2State* us = USB_CONTAINER_OF(dev, GunCon2State, dev); + + if (!us->cursor_path.empty()) + ImGuiManager::ClearSoftwareCursor(us->GetSoftwarePointerIndex()); + delete us; } @@ -359,8 +380,9 @@ namespace usb_lightgun std::tuple GunCon2State::CalculatePosition() { float pointer_x, pointer_y; - const std::pair abs_pos(InputManager::GetPointerAbsolutePosition(0)); - GSTranslateWindowToDisplayCoordinates(abs_pos.first, abs_pos.second, &pointer_x, &pointer_y); + const auto& [window_x, window_y] = + (has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0); + GSTranslateWindowToDisplayCoordinates(window_x, window_y, &pointer_x, &pointer_y); s16 pos_x, pos_y; if (pointer_x < 0.0f || pointer_y < 0.0f) @@ -403,6 +425,29 @@ namespace usb_lightgun return std::tie(pos_x, pos_y); } + std::pair GunCon2State::GetAbsolutePositionFromRelativeAxes() const + { + const float screen_rel_x = (((relative_pos[1] > 0.0f) ? relative_pos[1] : -relative_pos[0]) + 1.0f) * 0.5f; + const float screen_rel_y = (((relative_pos[3] > 0.0f) ? relative_pos[3] : -relative_pos[2]) + 1.0f) * 0.5f; + return std::make_pair( + screen_rel_x * ImGuiManager::GetWindowWidth(), screen_rel_y * ImGuiManager::GetWindowHeight()); + } + + u32 GunCon2State::GetSoftwarePointerIndex() const + { + return has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + port) : 0; + } + + void GunCon2State::UpdateSoftwarePointerPosition() + { + pxAssert(has_relative_binds); + if (cursor_path.empty()) + return; + + const auto& [window_x, window_y] = GetAbsolutePositionFromRelativeAxes(); + ImGuiManager::SetSoftwareCursorPosition(GetSoftwarePointerIndex(), window_x, window_y); + } + const char* GunCon2Device::Name() const { return TRANSLATE_NOOP("USB", "GunCon 2"); @@ -435,6 +480,8 @@ namespace usb_lightgun usb_desc_init(&s->dev); usb_ep_init(&s->dev); + UpdateSettings(&s->dev, si); + return &s->dev; fail: usb_hid_unrealize(&s->dev); @@ -457,6 +504,47 @@ namespace usb_lightgun s->scale_x = USB::GetConfigFloat(si, s->port, TypeName(), "scale_x", DEFAULT_SCALE_X) / 100.0f; s->scale_y = USB::GetConfigFloat(si, s->port, TypeName(), "scale_y", DEFAULT_SCALE_Y) / 100.0f; } + + // Pointer settings. + const std::string pointer_binding = USB::GetConfigString(si, s->port, TypeName(), "Pointer", ""); + std::string cursor_path(USB::GetConfigString(si, s->port, TypeName(), "cursor_path")); + const float cursor_scale = USB::GetConfigFloat(si, s->port, TypeName(), "cursor_scale", 1.0f); + u32 cursor_color = 0xFFFFFF; + if (std::string cursor_color_str(USB::GetConfigString(si, s->port, TypeName(), "cursor_color")); !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(); + } + + const s32 prev_pointer_index = s->GetSoftwarePointerIndex(); + + s->has_relative_binds = (USB::ConfigKeyExists(si, s->port, TypeName(), "RelativeLeft") || + USB::ConfigKeyExists(si, s->port, TypeName(), "RelativeRight") || + USB::ConfigKeyExists(si, s->port, TypeName(), "RelativeUp") || + USB::ConfigKeyExists(si, s->port, TypeName(), "RelativeDown")); + + const s32 new_pointer_index = s->GetSoftwarePointerIndex(); + + if (prev_pointer_index != new_pointer_index || s->cursor_path != cursor_path || + s->cursor_scale != cursor_scale || s->cursor_color != cursor_color) + { + if (prev_pointer_index != new_pointer_index) + ImGuiManager::ClearSoftwareCursor(prev_pointer_index); + + // Pointer changed, so need to update software cursor. + if (!cursor_path.empty()) + ImGuiManager::SetSoftwareCursor(new_pointer_index, cursor_path, cursor_scale, cursor_color); + else if (!s->cursor_path.empty()) + ImGuiManager::ClearSoftwareCursor(new_pointer_index); + + s->cursor_path = std::move(cursor_path); + s->cursor_scale = cursor_scale; + s->cursor_color = cursor_color; + } } float GunCon2Device::GetBindingValue(const USBDevice* dev, u32 bind_index) const @@ -471,11 +559,23 @@ namespace usb_lightgun { GunCon2State* s = USB_CONTAINER_OF(dev, GunCon2State, dev); - const u32 bit = 1u << bind_index; - if (value >= 0.5f) - s->button_state |= bit; - else - s->button_state &= ~bit; + if (bind_index < BID_RELATIVE_LEFT) + { + const u32 bit = 1u << bind_index; + if (value >= 0.5f) + s->button_state |= bit; + else + s->button_state &= ~bit; + } + else if (bind_index <= BID_RELATIVE_DOWN) + { + const u32 rel_index = bind_index - BID_RELATIVE_LEFT; + if (s->relative_pos[rel_index] != value) + { + s->relative_pos[rel_index] = value; + s->UpdateSoftwarePointerPosition(); + } + } } std::span GunCon2Device::Bindings(u32 subtype) const @@ -497,6 +597,10 @@ namespace usb_lightgun {"C", TRANSLATE_NOOP("USB", "C"), InputBindingInfo::Type::Button, BID_C, GenericInputBinding::Triangle}, {"Select", TRANSLATE_NOOP("USB", "Select"), InputBindingInfo::Type::Button, BID_SELECT, GenericInputBinding::Select}, {"Start", TRANSLATE_NOOP("USB", "Start"), InputBindingInfo::Type::Button, BID_START, GenericInputBinding::Start}, + {"RelativeLeft", TRANSLATE_NOOP("USB", "Relative Left"), InputBindingInfo::Type::HalfAxis, BID_RELATIVE_LEFT, GenericInputBinding::Unknown}, + {"RelativeRight", TRANSLATE_NOOP("USB", "Relative Right"), InputBindingInfo::Type::HalfAxis, BID_RELATIVE_RIGHT, GenericInputBinding::Unknown}, + {"RelativeUp", TRANSLATE_NOOP("USB", "Relative Up"), InputBindingInfo::Type::HalfAxis, BID_RELATIVE_UP, GenericInputBinding::Unknown}, + {"RelativeDown", TRANSLATE_NOOP("USB", "Relative Down"), InputBindingInfo::Type::HalfAxis, BID_RELATIVE_DOWN, GenericInputBinding::Unknown}, }; return bindings; @@ -505,16 +609,27 @@ namespace usb_lightgun std::span GunCon2Device::Settings(u32 subtype) const { static constexpr const SettingInfo info[] = { + {SettingInfo::Type::Path, "cursor_path", "Cursor Path", + TRANSLATE_NOOP("USB", "Sets the crosshair image that this lightgun will use. Setting a crosshair image " + "will disable the system cursor."), + ""}, + {SettingInfo::Type::Float, "cursor_scale", TRANSLATE_NOOP("USB", "Cursor Scale"), + TRANSLATE_NOOP("USB", "Scales the crosshair image set above."), "1", "0.01", "10", "0.01", "%.0f%%", + nullptr, nullptr, 100.0f}, + {SettingInfo::Type::String, "cursor_color", TRANSLATE_NOOP("USB", "Cursor Color"), + TRANSLATE_NOOP("USB", "Applys a color to the chosen crosshair images, can be used for multiple " + "players. Specify in HTML/CSS format (e.g. #aabbcc)"), + "#ffffff"}, {SettingInfo::Type::Boolean, "custom_config", TRANSLATE_NOOP("USB", "Manual Screen Configuration"), TRANSLATE_NOOP("USB", "Forces the use of the screen parameters below, instead of automatic parameters if available."), "false"}, {SettingInfo::Type::Float, "scale_x", TRANSLATE_NOOP("USB", "X Scale (Sensitivity)"), - "Scales the position to simulate CRT curvature.", "100", "0", "200", "0.1", "%.2f%%", nullptr, nullptr, - 1.0f}, + TRANSLATE_NOOP("USB", "Scales the position to simulate CRT curvature."), "100", "0", "200", "0.1", + "%.2f%%", nullptr, nullptr, 1.0f}, {SettingInfo::Type::Float, "scale_y", TRANSLATE_NOOP("USB", "Y Scale (Sensitivity)"), - "Scales the position to simulate CRT curvature.", "100", "0", "200", "0.1", "%.2f%%", nullptr, nullptr, - 1.0f}, + TRANSLATE_NOOP("USB", "Scales the position to simulate CRT curvature."), "100", "0", "200", "0.1", + "%.2f%%", nullptr, nullptr, 1.0f}, {SettingInfo::Type::Float, "center_x", TRANSLATE_NOOP("USB", "Center X"), TRANSLATE_NOOP("USB", "Sets the horizontal center position of the simulated screen."), "320", "0", "1024", "1", "%.0fpx", nullptr, nullptr, 1.0f},