GunCon: Add relative pointer binding

This commit is contained in:
Stenzek 2023-09-20 16:56:12 +10:00
parent e63b2eec38
commit 7e07d2feb8
21 changed files with 779 additions and 507 deletions

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "controller.h" #include "controller.h"
@ -26,18 +26,24 @@ static const Controller::ControllerInfo* s_controller_info[] = {
&GunCon::INFO, &PlayStationMouse::INFO, &GunCon::INFO, &PlayStationMouse::INFO,
}; };
Controller::Controller(u32 index) : m_index(index) {} Controller::Controller(u32 index) : m_index(index)
{
}
Controller::~Controller() = default; Controller::~Controller() = default;
void Controller::Reset() {} void Controller::Reset()
{
}
bool Controller::DoState(StateWrapper& sw, bool apply_input_state) bool Controller::DoState(StateWrapper& sw, bool apply_input_state)
{ {
return !sw.HasError(); return !sw.HasError();
} }
void Controller::ResetTransferState() {} void Controller::ResetTransferState()
{
}
bool Controller::Transfer(const u8 data_in, u8* data_out) bool Controller::Transfer(const u8 data_in, u8* data_out)
{ {
@ -50,7 +56,9 @@ float Controller::GetBindState(u32 index) const
return 0.0f; return 0.0f;
} }
void Controller::SetBindState(u32 index, float value) {} void Controller::SetBindState(u32 index, float value)
{
}
u32 Controller::GetButtonStateBits() const u32 Controller::GetButtonStateBits() const
{ {
@ -67,11 +75,8 @@ std::optional<u32> Controller::GetAnalogInputBytes() const
return std::nullopt; return std::nullopt;
} }
void Controller::LoadSettings(SettingsInterface& si, const char* section) {} void Controller::LoadSettings(SettingsInterface& si, const char* section)
bool Controller::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode)
{ {
return false;
} }
std::unique_ptr<Controller> Controller::Create(ControllerType type, u32 index) std::unique_ptr<Controller> Controller::Create(ControllerType type, u32 index)

View file

@ -87,9 +87,6 @@ public:
/// Loads/refreshes any per-controller settings. /// Loads/refreshes any per-controller settings.
virtual void LoadSettings(SettingsInterface& si, const char* section); 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. /// Creates a new controller of the specified type.
static std::unique_ptr<Controller> Create(ControllerType type, u32 index); static std::unique_ptr<Controller> Create(ControllerType type, u32 index);

View file

@ -1010,21 +1010,30 @@ void GPU::UpdateCommandTickEvent()
m_command_tick_event->SetIntervalAndSchedule(GPUTicksToSystemTicks(m_pending_command_ticks)); 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, void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
u32* out_line) const float* display_y) const
{ {
float left_padding, top_padding, scale; float scale;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), &left_padding, &top_padding, const Common::Rectangle<float> draw_rc = CalculateDrawRect(
&scale, nullptr); g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), nullptr, nullptr, &scale, nullptr);
// convert coordinates to active display region, then to full display region // convert coordinates to active display region, then to full display region
const float scaled_display_x = static_cast<float>(window_x) - left_padding; const float scaled_display_x = (window_x - draw_rc.left) / static_cast<float>(draw_rc.GetWidth());
const float scaled_display_y = static_cast<float>(window_y) - top_padding; const float scaled_display_y = (window_y - draw_rc.top) / static_cast<float>(draw_rc.GetHeight());
// scale back to internal resolution // scale back to internal resolution
float display_x = scaled_display_x / scale / x_scale; *display_x = scaled_display_x * static_cast<float>(m_crtc_state.display_width);
float display_y = scaled_display_y / scale; *display_y = scaled_display_y * static_cast<float>(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<float>(m_crtc_state.display_width),
*display_y / static_cast<float>(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) if (x_scale != 1.0f)
{ {
const float dw = static_cast<float>(m_crtc_state.display_width); const float dw = static_cast<float>(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 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<float>(m_crtc_state.display_width),
display_y / static_cast<float>(m_crtc_state.display_height));
if (display_x < 0 || static_cast<u32>(display_x) >= m_crtc_state.display_width || display_y < 0 || if (display_x < 0 || static_cast<u32>(display_x) >= m_crtc_state.display_width || display_y < 0 ||
static_cast<u32>(display_y) >= m_crtc_state.display_height) static_cast<u32>(display_y) >= m_crtc_state.display_height)
{ {

View file

@ -171,7 +171,9 @@ public:
static std::unique_ptr<GPU> CreateSoftwareRenderer(); static std::unique_ptr<GPU> CreateSoftwareRenderer();
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns. // 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, 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; u32* out_line) const;
// Returns the video clock frequency. // Returns the video clock frequency.

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "guncon.h" #include "guncon.h"
@ -6,11 +6,13 @@
#include "host.h" #include "host.h"
#include "system.h" #include "system.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h" #include "util/input_manager.h"
#include "util/state_wrapper.h" #include "util/state_wrapper.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/path.h" #include "common/path.h"
#include "common/string_util.h"
#include <array> #include <array>
@ -19,13 +21,21 @@
Log_SetChannel(GunCon); Log_SetChannel(GunCon);
#endif #endif
static constexpr std::array<u8, static_cast<size_t>(GunCon::Button::Count)> s_button_indices = {{13, 3, 14}}; static constexpr std::array<u8, static_cast<size_t>(GunCon::Binding::ButtonCount)> s_button_indices = {{13, 3, 14}};
GunCon::GunCon(u32 index) : Controller(index) 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 ControllerType GunCon::GetType() const
{ {
@ -71,12 +81,25 @@ float GunCon::GetBindState(u32 index) const
void GunCon::SetBindState(u32 index, float value) void GunCon::SetBindState(u32 index, float value)
{ {
const bool pressed = (value >= 0.5f); const bool pressed = (value >= 0.5f);
if (index == static_cast<u32>(Button::ShootOffscreen)) if (index == static_cast<u32>(Binding::ShootOffscreen))
{ {
if (m_shoot_offscreen != pressed) if (m_shoot_offscreen != pressed)
{ {
m_shoot_offscreen = pressed; m_shoot_offscreen = pressed;
SetBindState(static_cast<u32>(Button::Trigger), pressed); SetBindState(static_cast<u32>(Binding::Trigger), pressed);
}
return;
}
else if (index >= static_cast<u32>(Binding::ButtonCount))
{
if (index >= static_cast<u32>(Binding::BindingCount) || !m_has_relative_binds)
return;
if (m_relative_pos[index - static_cast<u32>(Binding::RelativeLeft)] != value)
{
m_relative_pos[index - static_cast<u32>(Binding::RelativeLeft)] = value;
UpdateSoftwarePointerPosition();
} }
return; return;
@ -181,18 +204,18 @@ bool GunCon::Transfer(const u8 data_in, u8* data_out)
void GunCon::UpdatePosition() void GunCon::UpdatePosition()
{ {
// get screen coordinates float display_x, display_y;
const auto& [fmouse_x, fmouse_y] = InputManager::GetPointerAbsolutePosition(0); const auto& [window_x, window_y] =
const s32 mouse_x = static_cast<s32>(fmouse_x); (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0);
const s32 mouse_y = static_cast<s32>(fmouse_y); g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y);
// are we within the active display area? // are we within the active display area?
u32 tick, line; u32 tick, line;
if (mouse_x < 0 || mouse_y < 0 || if (display_x < 0 || display_y < 0 ||
!g_gpu->ConvertScreenCoordinatesToBeamTicksAndLines(mouse_x, mouse_y, m_x_scale, &tick, &line) || !g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) ||
m_shoot_offscreen) 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_x = 0x01;
m_position_y = 0x0A; m_position_y = 0x0A;
return; return;
@ -202,8 +225,34 @@ void GunCon::UpdatePosition()
const double divider = static_cast<double>(g_gpu->GetCRTCFrequency()) / 8000000.0; const double divider = static_cast<double>(g_gpu->GetCRTCFrequency()) / 8000000.0;
m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider)); m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider));
m_position_y = static_cast<u16>(line); m_position_y = static_cast<u16>(line);
Log_DebugPrintf("Lightgun window coordinates %d,%d -> tick %u line %u 8mhz ticks %u", mouse_x, mouse_y, tick, line, Log_DebugPrintf("Lightgun window coordinates %.0f,%.0f -> tick %u line %u 8mhz ticks %u", display_x, display_y, tick,
m_position_x); line, m_position_x);
}
std::pair<float, float> 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> GunCon::Create(u32 index) std::unique_ptr<GunCon> GunCon::Create(u32 index)
@ -212,16 +261,25 @@ std::unique_ptr<GunCon> GunCon::Create(u32 index)
} }
static const Controller::ControllerBindingInfo s_binding_info[] = { 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<u32>(button), InputBindingInfo::Type::Button, genb \ name, display_name, static_cast<u32>(binding), InputBindingInfo::Type::Button, genb \
}
#define HALFAXIS(name, display_name, binding, genb) \
{ \
name, display_name, static_cast<u32>(binding), InputBindingInfo::Type::HalfAxis, genb \
} }
// clang-format off // clang-format off
BUTTON("Trigger", TRANSLATE_NOOP("GunCon", "Trigger"), GunCon::Button::Trigger, GenericInputBinding::R2), BUTTON("Trigger", TRANSLATE_NOOP("GunCon", "Trigger"), GunCon::Binding::Trigger, GenericInputBinding::R2),
BUTTON("ShootOffscreen", TRANSLATE_NOOP("GunCon", "Shoot Offscreen"), GunCon::Button::ShootOffscreen, GenericInputBinding::L2), BUTTON("ShootOffscreen", TRANSLATE_NOOP("GunCon", "Shoot Offscreen"), GunCon::Binding::ShootOffscreen, GenericInputBinding::L2),
BUTTON("A", TRANSLATE_NOOP("GunCon", "A"), GunCon::Button::A, GenericInputBinding::Cross), BUTTON("A", TRANSLATE_NOOP("GunCon", "A"), GunCon::Binding::A, GenericInputBinding::Cross),
BUTTON("B", TRANSLATE_NOOP("GunCon", "B"), GunCon::Button::B, GenericInputBinding::Circle), 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 // clang-format on
#undef BUTTON #undef BUTTON
@ -234,6 +292,10 @@ static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "CrosshairScale", TRANSLATE_NOOP("GunCon", "Crosshair Image Scale"), {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, TRANSLATE_NOOP("GunCon", "Scale of crosshair image on screen."), "1.0", "0.0001", "100.0", "0.10", "%.0f%%", nullptr,
100.0f}, 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"), {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", 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}}; "%.0f%%", nullptr, 100.0f}};
@ -251,24 +313,59 @@ void GunCon::LoadSettings(SettingsInterface& si, const char* section)
{ {
Controller::LoadSettings(si, 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<u32> cursor_color_opt(StringUtil::FromChars<u32>(
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__ #ifndef __ANDROID__
if (m_crosshair_image_path.empty()) if (cursor_path.empty())
m_crosshair_image_path = Path::Combine(EmuFolders::Resources, "images/crosshair.png"); cursor_path = Path::Combine(EmuFolders::Resources, "images/crosshair.png");
#endif #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); m_has_relative_binds = (si.ContainsValue(section, "RelativeLeft") || si.ContainsValue(section, "RelativeRight") ||
} si.ContainsValue(section, "RelativeUp") || si.ContainsValue(section, "RelativeDown"));
bool GunCon::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) const s32 new_pointer_index = GetSoftwarePointerIndex();
{
if (m_crosshair_image_path.empty()) if (prev_pointer_index != new_pointer_index || m_cursor_path != cursor_path || m_cursor_scale != cursor_scale ||
return false; m_cursor_color != cursor_color)
{
*image_path = m_crosshair_image_path; if (prev_pointer_index != new_pointer_index &&
*image_scale = m_crosshair_image_scale; static_cast<u32>(prev_pointer_index) < InputManager::MAX_SOFTWARE_CURSORS)
*relative_mode = false; {
return true; 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<u32>(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);
}
}
}
} }

View file

@ -10,13 +10,19 @@
class GunCon final : public Controller class GunCon final : public Controller
{ {
public: public:
enum class Button : u8 enum class Binding : u8
{ {
Trigger = 0, Trigger = 0,
A = 1, A = 1,
B = 2, B = 2,
ShootOffscreen = 3, ShootOffscreen = 3,
Count ButtonCount = 4,
RelativeLeft = 4,
RelativeRight = 5,
RelativeUp = 6,
RelativeDown = 7,
BindingCount = 8,
}; };
static const Controller::ControllerInfo INFO; static const Controller::ControllerInfo INFO;
@ -32,7 +38,6 @@ public:
bool DoState(StateWrapper& sw, bool apply_input_state) override; bool DoState(StateWrapper& sw, bool apply_input_state) override;
void LoadSettings(SettingsInterface& si, const char* section) 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; float GetBindState(u32 index) const override;
void SetBindState(u32 index, float value) override; void SetBindState(u32 index, float value) override;
@ -41,8 +46,6 @@ public:
bool Transfer(const u8 data_in, u8* data_out) override; bool Transfer(const u8 data_in, u8* data_out) override;
private: private:
void UpdatePosition();
enum class TransferState : u8 enum class TransferState : u8
{ {
Idle, Idle,
@ -56,15 +59,27 @@ private:
YMSB YMSB
}; };
std::string m_crosshair_image_path; void UpdatePosition();
float m_crosshair_image_scale = 1.0f;
// 0..1, not -1..1.
std::pair<float, float> 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_x_scale = 1.0f;
float m_relative_pos[4] = {};
// buttons are active low // buttons are active low
u16 m_button_state = UINT16_C(0xFFFF); u16 m_button_state = UINT16_C(0xFFFF);
u16 m_position_x = 0; u16 m_position_x = 0;
u16 m_position_y = 0; u16 m_position_y = 0;
bool m_shoot_offscreen = false; bool m_shoot_offscreen = false;
bool m_has_relative_binds = false;
TransferState m_transfer_state = TransferState::Idle; TransferState m_transfer_state = TransferState::Idle;
}; };

View file

@ -79,9 +79,6 @@ void ReportFormattedDebuggerMessage(const char* format, ...);
/// such as compiling shaders when starting up. /// such as compiling shaders when starting up.
void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); 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. /// Safely executes a function on the VM thread.
void RunOnCPUThread(std::function<void()> function, bool block = false); void RunOnCPUThread(std::function<void()> function, bool block = false);

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "playstation_mouse.h" #include "playstation_mouse.h"
@ -6,7 +6,6 @@
#include "host.h" #include "host.h"
#include "system.h" #include "system.h"
#include "util/input_manager.h"
#include "util/state_wrapper.h" #include "util/state_wrapper.h"
#include "common/assert.h" #include "common/assert.h"
@ -16,13 +15,11 @@
Log_SetChannel(PlayStationMouse); Log_SetChannel(PlayStationMouse);
static constexpr std::array<u8, static_cast<size_t>(PlayStationMouse::Button::Count)> s_button_indices = {{11, 10}}; static constexpr std::array<u8, static_cast<size_t>(PlayStationMouse::Binding::ButtonCount)> s_button_indices = {
{11, 10}};
PlayStationMouse::PlayStationMouse(u32 index) : Controller(index) PlayStationMouse::PlayStationMouse(u32 index) : Controller(index)
{ {
const auto& [x, y] = InputManager::GetPointerAbsolutePosition(0);
m_last_host_position_x = static_cast<s32>(x);
m_last_host_position_y = static_cast<s32>(y);
} }
PlayStationMouse::~PlayStationMouse() = default; PlayStationMouse::~PlayStationMouse() = default;
@ -43,11 +40,21 @@ bool PlayStationMouse::DoState(StateWrapper& sw, bool apply_input_state)
return false; return false;
u16 button_state = m_button_state; u16 button_state = m_button_state;
u8 delta_x = m_delta_x; float delta_x = m_delta_x;
u8 delta_y = m_delta_y; float delta_y = m_delta_y;
sw.Do(&button_state); sw.Do(&button_state);
if (sw.GetVersion() >= 60)
{
sw.Do(&delta_x); sw.Do(&delta_x);
sw.Do(&delta_y); sw.Do(&delta_y);
}
else
{
u8 dummy = 0;
sw.Do(&dummy);
sw.Do(&dummy);
}
if (apply_input_state) if (apply_input_state)
{ {
m_button_state = button_state; m_button_state = button_state;
@ -70,8 +77,15 @@ float PlayStationMouse::GetBindState(u32 index) const
void PlayStationMouse::SetBindState(u32 index, float value) void PlayStationMouse::SetBindState(u32 index, float value)
{ {
if (index > s_button_indices.size()) if (index >= s_button_indices.size())
{
if (index == static_cast<u32>(Binding::PointerX))
m_delta_x += value;
else if (index == static_cast<u32>(Binding::PointerY))
m_delta_y += value;
return; return;
}
if (value >= 0.5f) if (value >= 0.5f)
m_button_state &= ~(u16(1) << s_button_indices[index]); 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: case TransferState::DeltaX:
{ {
UpdatePosition(); const float delta_x =
*data_out = static_cast<u8>(m_delta_x); std::clamp(std::floor(m_delta_x * m_sensitivity_x), static_cast<float>(std::numeric_limits<s8>::min()),
static_cast<float>(std::numeric_limits<s8>::max()));
m_delta_x -= delta_x / m_sensitivity_x;
*data_out = static_cast<s8>(delta_x);
m_transfer_state = TransferState::DeltaY; m_transfer_state = TransferState::DeltaY;
return true; return true;
} }
case TransferState::DeltaY: case TransferState::DeltaY:
{ {
*data_out = static_cast<u8>(m_delta_y); const float delta_y =
std::clamp(std::floor(m_delta_y * m_sensitivity_y), static_cast<float>(std::numeric_limits<s8>::min()),
static_cast<float>(std::numeric_limits<s8>::max()));
m_delta_y -= delta_y / m_sensitivity_x;
*data_out = static_cast<s8>(delta_y);
m_transfer_state = TransferState::Idle; m_transfer_state = TransferState::Idle;
return false; 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 Controller::LoadSettings(si, section);
const auto& [fmouse_x, fmouse_y] = InputManager::GetPointerAbsolutePosition(0);
const s32 mouse_x = static_cast<s32>(fmouse_x);
const s32 mouse_y = static_cast<s32>(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;
if (delta_x != 0 || delta_y != 0) m_sensitivity_x = si.GetFloatValue(section, "SensitivityX", 1.0f);
Log_DevPrintf("dx=%d, dy=%d", delta_x, delta_y); m_sensitivity_y = si.GetFloatValue(section, "SensitivityY", 1.0f);
m_delta_x = static_cast<s8>(std::clamp<s32>(delta_x, std::numeric_limits<s8>::min(), std::numeric_limits<s8>::max()));
m_delta_y = static_cast<s8>(std::clamp<s32>(delta_y, std::numeric_limits<s8>::min(), std::numeric_limits<s8>::max()));
} }
std::unique_ptr<PlayStationMouse> PlayStationMouse::Create(u32 index) std::unique_ptr<PlayStationMouse> PlayStationMouse::Create(u32 index)
@ -188,17 +199,20 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
} }
// clang-format off // clang-format off
BUTTON("Left", TRANSLATE_NOOP("PlayStationMouse", "Left Button"), PlayStationMouse::Button::Left, GenericInputBinding::Cross), { "Pointer", TRANSLATE_NOOP("PlaystationMouse", "Pointer"), static_cast<u32>(PlayStationMouse::Binding::PointerX), InputBindingInfo::Type::Pointer, GenericInputBinding::Unknown },
BUTTON("Right", TRANSLATE_NOOP("PlayStationMouse", "Right Button"), PlayStationMouse::Button::Right, GenericInputBinding::Circle), 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 // clang-format on
#undef BUTTON #undef BUTTON
}; };
static const SettingInfo s_settings[] = { static const SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "RelativeMouseMode", TRANSLATE_NOOP("PlayStationMouse", "Relative Mouse Mode"), {SettingInfo::Type::Float, "SensitivityX", TRANSLATE_NOOP("PlayStationMouse", "Horizontal Sensitivity"),
TRANSLATE_NOOP("PlayStationMouse", "Locks the mouse cursor to the window, use for FPS games."), "false", nullptr, TRANSLATE_NOOP("PlayStationMouse", "Adjusts the correspondance between physical and virtual mouse movement."), "1.0",
nullptr, nullptr, nullptr, nullptr, 0.0f}, "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, const Controller::ControllerInfo PlayStationMouse::INFO = {ControllerType::PlayStationMouse,
@ -209,16 +223,3 @@ const Controller::ControllerInfo PlayStationMouse::INFO = {ControllerType::PlayS
s_settings, s_settings,
countof(s_settings), countof(s_settings),
Controller::VibrationCapabilities::NoVibration}; 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;
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once #pragma once
@ -10,11 +10,15 @@
class PlayStationMouse final : public Controller class PlayStationMouse final : public Controller
{ {
public: public:
enum class Button : u8 enum class Binding : u8
{ {
Left = 0, Left = 0,
Right = 1, Right = 1,
Count ButtonCount = 2,
PointerX = 2,
PointerY = 3,
BindingCount = 4,
}; };
static const Controller::ControllerInfo INFO; static const Controller::ControllerInfo INFO;
@ -36,11 +40,8 @@ public:
bool Transfer(const u8 data_in, u8* data_out) override; bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(SettingsInterface& si, const char* section) override; void LoadSettings(SettingsInterface& si, const char* section) override;
bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) override;
private: private:
void UpdatePosition();
enum class TransferState : u8 enum class TransferState : u8
{ {
Idle, Idle,
@ -52,15 +53,13 @@ private:
DeltaY DeltaY
}; };
s32 m_last_host_position_x = 0; float m_sensitivity_x = 1.0f;
s32 m_last_host_position_y = 0; float m_sensitivity_y = 1.0f;
// buttons are active low // buttons are active low
u16 m_button_state = UINT16_C(0xFFFF); u16 m_button_state = UINT16_C(0xFFFF);
s8 m_delta_x = 0; float m_delta_x = 0;
s8 m_delta_y = 0; float m_delta_y = 0;
TransferState m_transfer_state = TransferState::Idle; TransferState m_transfer_state = TransferState::Idle;
bool m_use_relative_mode = false;
}; };

View file

@ -5,7 +5,7 @@
#include "types.h" #include "types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544; 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 constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION); static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View file

@ -1398,7 +1398,6 @@ bool System::BootSystem(SystemBootParameters parameters)
// Good to go. // Good to go.
s_state = State::Running; s_state = State::Running;
UpdateSoftwareCursor();
SPU::GetOutputStream()->SetPaused(false); SPU::GetOutputStream()->SetPaused(false);
FullscreenUI::OnSystemStarted(); FullscreenUI::OnSystemStarted();
@ -1632,7 +1631,6 @@ void System::DestroySystem()
if (s_keep_gpu_device_on_shutdown && g_gpu_device) if (s_keep_gpu_device_on_shutdown && g_gpu_device)
{ {
g_gpu_device->SetDisplayMaxFPS(0.0f); g_gpu_device->SetDisplayMaxFPS(0.0f);
UpdateSoftwareCursor();
} }
else else
{ {
@ -3673,17 +3671,13 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
{ {
UpdateControllers(); UpdateControllers();
ResetControllers(); ResetControllers();
UpdateSoftwareCursor();
controllers_updated = true; controllers_updated = true;
} }
} }
}
if (IsValid() && !controllers_updated) if (IsValid() && !controllers_updated)
{
UpdateControllerSettings(); UpdateControllerSettings();
UpdateSoftwareCursor();
}
}
if (g_settings.multitap_mode != old_settings.multitap_mode) if (g_settings.multitap_mode != old_settings.multitap_mode)
UpdateMultitaps(); UpdateMultitaps();
@ -4562,38 +4556,6 @@ void System::ToggleSoftwareRendering()
ResetPerformanceCounters(); 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*/) void System::RequestDisplaySize(float scale /*= 0.0f*/)
{ {
if (!IsValid()) if (!IsValid())
@ -4638,6 +4600,8 @@ bool System::PresentDisplay(bool allow_skip_present)
FullscreenUI::Render(); FullscreenUI::Render();
ImGuiManager::RenderTextOverlays(); ImGuiManager::RenderTextOverlays();
ImGuiManager::RenderOSDMessages(); ImGuiManager::RenderOSDMessages();
if (s_state == State::Running)
ImGuiManager::RenderSoftwareCursors(); ImGuiManager::RenderSoftwareCursors();
} }

View file

@ -456,9 +456,6 @@ bool ShouldUseVSync();
/// Quick switch between software and hardware rendering. /// Quick switch between software and hardware rendering.
void ToggleSoftwareRendering(); void ToggleSoftwareRendering();
/// Updates software cursor state, based on controllers.
void UpdateSoftwareCursor();
/// Resizes the render window to the display size, with an optional scale. /// 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. /// 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); void RequestDisplaySize(float scale = 0.0f);

View file

@ -773,16 +773,7 @@ void Host::OnAchievementsHardcoreModeChanged(bool enabled)
void Host::SetMouseMode(bool relative, bool hide_cursor) void Host::SetMouseMode(bool relative, bool hide_cursor)
{ {
#if 0 // noop
// TODO: Find a better home for this.
if (InputManager::HasPointerAxisBinds())
{
relative = true;
hide_cursor = true;
}
// emit g_emu_thread->mouseModeRequested(relative, hide_cursor);
#endif
} }
void Host::PumpMessagesOnCPUThread() void Host::PumpMessagesOnCPUThread()

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1100</width> <width>1010</width>
<height>504</height> <height>418</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -18,236 +18,15 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>1100</width> <width>1000</width>
<height>500</height> <height>400</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_35"> <layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin"> <item row="0" column="2">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="2" rowspan="4">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_17">
<property name="title">
<string>B</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="B">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_19">
<property name="title">
<string>A</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="A">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>266</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/controllers/guncon.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout_27"/>
</item>
<item row="3" column="1">
<layout class="QGridLayout" name="gridLayout_32">
<item row="0" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0" rowspan="4">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Trigger</string> <string>Trigger</string>
@ -275,13 +54,13 @@
<widget class="InputBindingWidget" name="ShootOffscreen"> <widget class="InputBindingWidget" name="ShootOffscreen">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>150</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>100</width> <width>150</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -315,13 +94,13 @@
<widget class="InputBindingWidget" name="Trigger"> <widget class="InputBindingWidget" name="Trigger">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>150</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>100</width> <width>150</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -336,8 +115,8 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item row="2" column="0" colspan="3">
<spacer name="verticalSpacer_5"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
@ -349,20 +128,368 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Side Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_17">
<property name="title">
<string>B</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="B">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_19">
<property name="title">
<string>A</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="A">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string extracomment="Try to use Sony's official terminology for this. A good place to start would be in the console or the DualShock 2's manual. If this element was officially translated to your language by Sony in later DualShocks, you may use that term.">Relative Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Down</string>
</property>
<layout class="QGridLayout" name="gridLayout_12">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeDown">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_12">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_13">
<property name="title">
<string>Up</string>
</property>
<layout class="QGridLayout" name="gridLayout_14">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeUp">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_14">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_15">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="RelativeRight">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>266</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/controllers/guncon.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
<item row="1" column="1"> <item row="0" column="0">
<spacer name="verticalSpacer_6"> <widget class="QGroupBox" name="groupBox_15">
<property name="orientation"> <property name="title">
<enum>Qt::Vertical</enum> <string>Pointer Setup</string>
</property> </property>
<property name="sizeHint" stdset="0"> <layout class="QVBoxLayout" name="verticalLayout">
<size> <item>
<width>20</width> <widget class="QLabel" name="label_2">
<height>40</height> <property name="text">
</size> <string>&lt;p&gt;By default, GunCon will use the mouse pointer. To use the mouse, you &lt;strong&gt;do not&lt;/strong&gt; need to configure any bindings apart from the trigger and buttons.&lt;/p&gt;
&lt;p&gt;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 &lt;strong&gt;left unbound&lt;/strong&gt;.&lt;/p&gt;</string>
</property> </property>
</spacer> <property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "controllerbindingwidgets.h" #include "controllerbindingwidgets.h"
@ -597,7 +597,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidge
} }
SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, sb, section, std::move(key_name), si.multiplier, SettingWidgetBinder::BindWidgetToNormalizedSetting(sif, sb, section, std::move(key_name), si.multiplier,
si.FloatDefaultValue() * multiplier); si.FloatDefaultValue());
} }
else else
{ {

View file

@ -515,6 +515,7 @@ void MainWindow::onSystemStarted()
updateEmulationActions(false, true, Achievements::IsHardcoreModeActive()); updateEmulationActions(false, true, Achievements::IsHardcoreModeActive());
updateWindowTitle(); updateWindowTitle();
updateStatusBarWidgetVisibility(); updateStatusBarWidgetVisibility();
updateDisplayWidgetCursor();
} }
void MainWindow::onSystemPaused() void MainWindow::onSystemPaused()

View file

@ -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) 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); emit g_emu_thread->mouseModeRequested(relative, hide_cursor);
} }

View file

@ -71,6 +71,8 @@ static std::vector<u8> s_standard_font_data;
static std::vector<u8> s_fixed_font_data; static std::vector<u8> s_fixed_font_data;
static std::vector<u8> s_icon_font_data; static std::vector<u8> s_icon_font_data;
static float s_window_width;
static float s_window_height;
static Common::Timer s_last_render_time; static Common::Timer s_last_render_time;
// cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events // 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; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
#endif #endif
s_window_width = static_cast<float>(g_gpu_device->GetWindowWidth());
s_window_height = static_cast<float>(g_gpu_device->GetWindowHeight());
io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
io.DisplaySize.x = static_cast<float>(g_gpu_device->GetWindowWidth()); io.DisplaySize = ImVec2(s_window_width, s_window_height);
io.DisplaySize.y = static_cast<float>(g_gpu_device->GetWindowHeight());
SetKeyMap(); SetKeyMap();
SetStyle(); SetStyle();
@ -197,12 +200,24 @@ void ImGuiManager::Shutdown()
ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr); ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
} }
float ImGuiManager::GetWindowWidth()
{
return s_window_width;
}
float ImGuiManager::GetWindowHeight()
{
return s_window_height;
}
void ImGuiManager::WindowResized() void ImGuiManager::WindowResized()
{ {
const u32 new_width = g_gpu_device ? g_gpu_device->GetWindowWidth() : 0; const u32 new_width = g_gpu_device ? g_gpu_device->GetWindowWidth() : 0;
const u32 new_height = g_gpu_device ? g_gpu_device->GetWindowHeight() : 0; const u32 new_height = g_gpu_device ? g_gpu_device->GetWindowHeight() : 0;
ImGui::GetIO().DisplaySize = ImVec2(static_cast<float>(new_width), static_cast<float>(new_height)); s_window_width = static_cast<float>(new_width);
s_window_height = static_cast<float>(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 // restart imgui frame on the new window size to pick it up, otherwise we draw to the old size
ImGui::EndFrame(); ImGui::EndFrame();
@ -727,7 +742,7 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
const float margin = std::ceil(10.0f * scale); const float margin = std::ceil(10.0f * scale);
const float padding = std::ceil(8.0f * scale); const float padding = std::ceil(8.0f * scale);
const float rounding = std::ceil(5.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_x = margin;
float position_y = margin; float position_y = margin;
@ -938,7 +953,7 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value
void ImGuiManager::CreateSoftwareCursorTextures() void ImGuiManager::CreateSoftwareCursorTextures()
{ {
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) for (u32 i = 0; i < static_cast<u32>(s_software_cursors.size()); i++)
{ {
if (!s_software_cursors[i].image_path.empty()) if (!s_software_cursors[i].image_path.empty())
UpdateSoftwareCursorTexture(i); UpdateSoftwareCursorTexture(i);
@ -947,10 +962,8 @@ void ImGuiManager::CreateSoftwareCursorTextures()
void ImGuiManager::DestroySoftwareCursorTextures() void ImGuiManager::DestroySoftwareCursorTextures()
{ {
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) for (SoftwareCursor& sc : s_software_cursors)
{ sc.texture.reset();
s_software_cursors[i].texture.reset();
}
} }
void ImGuiManager::UpdateSoftwareCursorTexture(u32 index) void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)

View file

@ -30,6 +30,10 @@ bool Initialize(float global_scale, bool show_osd_messages);
/// Frees all ImGui resources. /// Frees all ImGui resources.
void Shutdown(); 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. /// Updates internal state when the window is size.
void WindowResized(); void WindowResized();

View file

@ -174,6 +174,9 @@ static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis:
s_pointer_state; s_pointer_state;
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale; static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
static std::vector<std::pair<u32, PointerMoveCallback>> s_pointer_move_callbacks;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Binding Parsing // Binding Parsing
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -699,6 +702,13 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
{ {
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i]; const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bi.name)); const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bi.name));
switch (bi.type)
{
case InputBindingInfo::Type::Button:
case InputBindingInfo::Type::HalfAxis:
case InputBindingInfo::Type::Axis:
{
if (!bindings.empty()) if (!bindings.empty())
{ {
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) { AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) {
@ -711,6 +721,43 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
}}); }});
} }
} }
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<u32> 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<u32>(bi.type));
break;
}
}
for (u32 macro_button_index = 0; macro_button_index < NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++) for (u32 macro_button_index = 0; macro_button_index < NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++)
{ {
@ -996,6 +1043,8 @@ bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInpu
void InputManager::GenerateRelativeMouseEvents() void InputManager::GenerateRelativeMouseEvents()
{ {
const bool system_running = System::IsRunning();
for (u32 device = 0; device < MAX_POINTER_DEVICES; device++) for (u32 device = 0; device < MAX_POINTER_DEVICES; device++)
{ {
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++) for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
@ -1011,12 +1060,24 @@ void InputManager::GenerateRelativeMouseEvents()
continue; continue;
} }
if (!system_running)
continue;
const float value = std::clamp(unclamped_value, -1.0f, 1.0f); const float value = std::clamp(unclamped_value, -1.0f, 1.0f);
if (value != state.last_value) if (value != state.last_value)
{ {
state.last_value = value; state.last_value = value;
InvokeEvents(key, value, GenericInputBinding::Unknown); InvokeEvents(key, value, GenericInputBinding::Unknown);
} }
if (delta != 0.0f)
{
for (const std::pair<u32, PointerMoveCallback>& 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() 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<u32>(InputPointerAxis::X) && key.data <= static_cast<u32>(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() bool InputManager::IsUsingRawInput()
@ -1074,22 +1152,6 @@ bool InputManager::IsUsingRawInput()
#endif #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<u32>(InputPointerAxis::X) && key.data <= static_cast<u32>(InputPointerAxis::Y))
{
return true;
}
}
return false;
}
void InputManager::SetDefaultSourceConfig(SettingsInterface& si) void InputManager::SetDefaultSourceConfig(SettingsInterface& si)
{ {
si.ClearSection("InputSources"); si.ClearSection("InputSources");
@ -1546,6 +1608,7 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
s_binding_map.clear(); s_binding_map.clear();
s_pad_vibration_array.clear(); s_pad_vibration_array.clear();
s_pointer_move_callbacks.clear();
// Hotkeys use the base configuration, except if the custom hotkeys option is enabled. // Hotkeys use the base configuration, except if the custom hotkeys option is enabled.
const bool use_profile_hotkeys = si.GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false); const bool use_profile_hotkeys = si.GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
@ -1573,6 +1636,8 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
default_scale), default_scale),
1.0f); 1.0f);
} }
UpdateHostMouseMode();
} }
bool InputManager::MigrateBindings(SettingsInterface& si) bool InputManager::MigrateBindings(SettingsInterface& si)

View file

@ -326,9 +326,6 @@ void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns true if the raw input source is being used. /// Returns true if the raw input source is being used.
bool IsUsingRawInput(); bool IsUsingRawInput();
/// Returns true if any bindings are present which require relative mouse movement.
bool HasPointerAxisBinds();
/// Restores default configuration. /// Restores default configuration.
void SetDefaultSourceConfig(SettingsInterface& si); 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. /// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier); 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 } // namespace Host