ImGuiManager: Add software cursor support

Can be used to render a crosshair for GunCon.
This commit is contained in:
Stenzek 2023-07-23 15:07:46 +10:00 committed by Connor McLaughlin
parent 49d3338d4a
commit 24171787f8
10 changed files with 171 additions and 14 deletions

View File

@ -257,7 +257,7 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
{
}
void Host::SetRelativeMouseMode(bool enabled)
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
}

View File

@ -414,7 +414,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
connect(thread, &EmuThread::onMouseModeRequested, this, &MainWindow::mouseModeRequested);
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
connect(thread, &EmuThread::onVMPaused, this, &MainWindow::onVMPaused);
@ -887,7 +887,8 @@ bool MainWindow::isRenderingToMain() const
bool MainWindow::shouldHideMouseCursor() const
{
return (isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false)) || m_relative_mouse_mode;
return ((isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false)) ||
m_relative_mouse_mode || m_hide_mouse_cursor);
}
bool MainWindow::shouldHideMainWindow() const
@ -2003,12 +2004,13 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
}
void MainWindow::relativeMouseModeRequested(bool enabled)
void MainWindow::mouseModeRequested(bool relative_mode, bool hide_cursor)
{
if (m_relative_mouse_mode == enabled)
if (m_relative_mouse_mode == relative_mode && m_hide_mouse_cursor == hide_cursor)
return;
m_relative_mouse_mode = enabled;
m_relative_mouse_mode = relative_mode;
m_hide_mouse_cursor = hide_cursor;
if (m_display_widget && !s_vm_paused)
updateDisplayWidgetCursor();
}

View File

@ -124,7 +124,7 @@ private Q_SLOTS:
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void displayResizeRequested(qint32 width, qint32 height);
void relativeMouseModeRequested(bool enabled);
void mouseModeRequested(bool relative_mode, bool hide_cursor);
void releaseRenderWindow();
void focusDisplayWidget();
@ -293,6 +293,7 @@ private:
bool m_display_created = false;
bool m_relative_mouse_mode = false;
bool m_hide_mouse_cursor = false;
bool m_was_paused_on_surface_loss = false;
bool m_was_disc_change_request = false;
bool m_is_closing = false;

View File

@ -1468,9 +1468,9 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
emit g_emu_thread->onInputDeviceDisconnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()));
}
void Host::SetRelativeMouseMode(bool enabled)
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
emit g_emu_thread->onRelativeMouseModeRequested(enabled);
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
}
//////////////////////////////////////////////////////////////////////////

View File

@ -119,7 +119,7 @@ Q_SIGNALS:
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void onRelativeMouseModeRequested(bool enabled);
void onMouseModeRequested(bool relative_mode, bool hide_cursor);
/// Called when the VM is starting initialization, but has not been completed yet.
void onVMStarting();

View File

@ -38,6 +38,7 @@
#include "fmt/core.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "common/Image.h"
#include <chrono>
#include <cmath>
@ -47,6 +48,17 @@
namespace ImGuiManager
{
struct SoftwareCursor
{
std::string image_path;
std::unique_ptr<GSTexture> texture;
u32 color;
float scale;
float extent_x;
float extent_y;
std::pair<float, float> pos;
};
static void SetStyle();
static void SetKeyMap();
static bool LoadFontData();
@ -57,6 +69,11 @@ namespace ImGuiManager
static bool AddIconFonts(float size);
static void AcquirePendingOSDMessages();
static void DrawOSDMessages();
static void CreateSoftwareCursorTextures();
static void UpdateSoftwareCursorTexture(u32 index);
static void DestroySoftwareCursorTextures();
static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos);
static void DrawSoftwareCursors();
} // namespace ImGuiManager
static float s_global_scale = 1.0f;
@ -86,6 +103,8 @@ static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
// need to keep track of this, so we can reinitialize on renderer switch
static bool s_fullscreen_ui_was_initialized = false;
static std::array<ImGuiManager::SoftwareCursor, InputManager::MAX_SOFTWARE_CURSORS> s_software_cursors = {};
void ImGuiManager::SetFontPath(std::string path)
{
s_font_path = std::move(path);
@ -146,6 +165,7 @@ bool ImGuiManager::Initialize()
if (add_fullscreen_fonts)
InitializeFullscreenUI();
CreateSoftwareCursorTextures();
return true;
}
@ -157,6 +177,8 @@ bool ImGuiManager::InitializeFullscreenUI()
void ImGuiManager::Shutdown(bool clear_state)
{
DestroySoftwareCursorTextures();
FullscreenUI::Shutdown(clear_state);
ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
if (clear_state)
@ -672,6 +694,7 @@ void ImGuiManager::RenderOSD()
AcquirePendingOSDMessages();
DrawOSDMessages();
DrawSoftwareCursors();
}
float ImGuiManager::GetGlobalScale()
@ -810,3 +833,112 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value
return true;
}
void ImGuiManager::CreateSoftwareCursorTextures()
{
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++)
{
if (!s_software_cursors[i].image_path.empty())
UpdateSoftwareCursorTexture(i);
}
}
void ImGuiManager::DestroySoftwareCursorTextures()
{
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++)
{
s_software_cursors[i].texture.reset();
}
}
void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
{
SoftwareCursor& sc = s_software_cursors[index];
if (sc.image_path.empty())
{
sc.texture.reset();
return;
}
Common::RGBA8Image image;
if (!image.LoadFromFile(sc.image_path.c_str()))
{
Console.Error("Failed to load software cursor %u image '%s'", index, sc.image_path.c_str());
return;
}
sc.texture = std::unique_ptr<GSTexture>(g_gs_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, GSTexture::Format::Color));
if (!sc.texture)
{
Console.Error(
"Failed to upload %ux%u software cursor %u image '%s'", image.GetWidth(), image.GetHeight(), index, sc.image_path.c_str());
return;
}
sc.texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetByteStride(), 0);
sc.extent_x = std::ceil(static_cast<float>(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f;
sc.extent_y = std::ceil(static_cast<float>(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f;
}
void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos)
{
if (!sc.texture)
return;
const ImVec2 min(pos.first - sc.extent_x, pos.second - sc.extent_y);
const ImVec2 max(pos.first + sc.extent_x, pos.second + sc.extent_y);
ImDrawList* dl = ImGui::GetForegroundDrawList();
dl->AddImage(
reinterpret_cast<ImTextureID>(sc.texture.get()->GetNativeHandle()), min, max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), sc.color);
}
void ImGuiManager::DrawSoftwareCursors()
{
// This one's okay to race, worst that happens is we render the wrong number of cursors for a frame.
const u32 pointer_count = InputManager::MAX_POINTER_DEVICES;
for (u32 i = 0; i < pointer_count; i++)
DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i));
for (u32 i = InputManager::MAX_POINTER_DEVICES; i < InputManager::MAX_SOFTWARE_CURSORS; i++)
DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos);
}
void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color)
{
MTGS::RunOnGSThread([index, image_path = std::move(image_path), image_scale, multiply_color]() {
pxAssert(index < std::size(s_software_cursors));
SoftwareCursor& sc = s_software_cursors[index];
sc.color = multiply_color | 0xFF000000;
if (sc.image_path == image_path && sc.scale == image_scale)
return;
const bool is_hiding_or_showing = (image_path.empty() != sc.image_path.empty());
sc.image_path = std::move(image_path);
sc.scale = image_scale;
if (MTGS::IsOpen())
UpdateSoftwareCursorTexture(index);
// Hide the system cursor when we activate a software cursor.
if (is_hiding_or_showing && index == 0)
Host::RunOnCPUThread(&InputManager::UpdateHostMouseMode);
});
}
bool ImGuiManager::HasSoftwareCursor(u32 index)
{
return (index < s_software_cursors.size() && !s_software_cursors[index].image_path.empty());
}
void ImGuiManager::ClearSoftwareCursor(u32 index)
{
SetSoftwareCursor(index, std::string(), 0.0f, 0);
}
void ImGuiManager::SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y)
{
pxAssert(index >= InputManager::MAX_POINTER_DEVICES);
SoftwareCursor& sc = s_software_cursors[index];
sc.pos.first = pos_x;
sc.pos.second = pos_y;
}

View File

@ -102,6 +102,14 @@ namespace ImGuiManager
/// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation.
bool ProcessGenericInputEvent(GenericInputBinding key, float value);
/// Sets an image and scale for a software cursor. Software cursors can be used for things like crosshairs.
void SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color = 0xFFFFFF);
bool HasSoftwareCursor(u32 index);
void ClearSoftwareCursor(u32 index);
/// Sets the position of a software cursor, used when we have relative coordinates such as controllers.
void SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y);
} // namespace ImGuiManager
namespace Host

View File

@ -1309,6 +1309,11 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
for (u32 port = 0; port < USB::NUM_PORTS; port++)
AddUSBBindings(binding_si, port);
UpdateHostMouseMode();
}
void InputManager::UpdateHostMouseMode()
{
// 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)
@ -1324,7 +1329,9 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
}
}
}
Host::SetRelativeMouseMode(has_relative_mode_bindings);
const bool has_software_cursor = ImGuiManager::HasSoftwareCursor(0);
Host::SetMouseMode(has_relative_mode_bindings, has_relative_mode_bindings || has_software_cursor);
}
// ------------------------------------------------------------------------

View File

@ -170,6 +170,10 @@ namespace InputManager
static constexpr u32 MAX_POINTER_DEVICES = 1;
static constexpr u32 MAX_POINTER_BUTTONS = 3;
/// Maximum number of software cursors. We allocate an extra two for USB devices with
/// positioning data from the controller instead of a mouse.
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_BUTTONS + 2;
/// Returns a pointer to the external input source class, if present.
InputSource* GetInputSourceInterface(InputSourceType type);
@ -287,6 +291,9 @@ namespace InputManager
/// Updates relative pointer position. Can call from the UI thread, use when host supports relative coordinate reporting.
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
/// Updates host mouse mode (relative/cursor hiding).
void UpdateHostMouseMode();
/// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name);
@ -305,6 +312,6 @@ namespace Host
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier);
/// Enables relative mouse mode in the host.
void SetRelativeMouseMode(bool enabled);
/// Enables relative mouse mode in the host, and/or hides the cursor.
void SetMouseMode(bool relative_mode, bool hide_cursor);
} // namespace Host

View File

@ -95,7 +95,7 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
{
}
void Host::SetRelativeMouseMode(bool enabled)
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
}