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)
#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<u32> 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> Controller::Create(ControllerType type, u32 index)

View file

@ -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<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));
}
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<float> 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<float>(window_x) - left_padding;
const float scaled_display_y = static_cast<float>(window_y) - top_padding;
const float scaled_display_x = (window_x - draw_rc.left) / static_cast<float>(draw_rc.GetWidth());
const float scaled_display_y = (window_y - draw_rc.top) / static_cast<float>(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<float>(m_crtc_state.display_width);
*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)
{
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
}
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 ||
static_cast<u32>(display_y) >= m_crtc_state.display_height)
{

View file

@ -171,8 +171,10 @@ public:
static std::unique_ptr<GPU> 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;

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)
#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 <array>
@ -19,13 +21,21 @@
Log_SetChannel(GunCon);
#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() = 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<u32>(Button::ShootOffscreen))
if (index == static_cast<u32>(Binding::ShootOffscreen))
{
if (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;
@ -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<s32>(fmouse_x);
const s32 mouse_y = static_cast<s32>(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<double>(g_gpu->GetCRTCFrequency()) / 8000000.0;
m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider));
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,
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<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)
@ -212,16 +261,25 @@ std::unique_ptr<GunCon> 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<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
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<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__
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<u32>(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<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
{
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<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_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;
};

View file

@ -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<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)
#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<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)
{
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;
@ -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<u32>(Binding::PointerX))
m_delta_x += value;
else if (index == static_cast<u32>(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<u8>(m_delta_x);
const float 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;
return true;
}
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;
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<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;
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<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()));
m_sensitivity_x = si.GetFloatValue(section, "SensitivityX", 1.0f);
m_sensitivity_y = si.GetFloatValue(section, "SensitivityY", 1.0f);
}
std::unique_ptr<PlayStationMouse> 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<u32>(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};

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)
#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;
};

View file

@ -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);

View file

@ -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.

View file

@ -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);

View file

@ -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()

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>504</height>
<width>1010</width>
<height>418</height>
</rect>
</property>
<property name="sizePolicy">
@ -18,29 +18,377 @@
</property>
<property name="minimumSize">
<size>
<width>1100</width>
<height>500</height>
<width>1000</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_35">
<property name="leftMargin">
<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>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<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="ShootOffscreen">
<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_2">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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="Trigger">
<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="2" column="0" colspan="3">
<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>
<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>
@ -53,97 +401,8 @@
</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">
<item row="0" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@ -155,11 +414,7 @@
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -172,8 +427,8 @@
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<item row="1" column="1">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -197,7 +452,7 @@
</property>
</widget>
</item>
<item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -212,157 +467,29 @@
</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">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Fire Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<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="ShootOffscreen">
<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_2">
<property name="title">
<string>Fire</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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="Trigger">
<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_5">
<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="1" column="1">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Pointer Setup</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<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 name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</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)
#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
{

View file

@ -515,6 +515,7 @@ void MainWindow::onSystemStarted()
updateEmulationActions(false, true, Achievements::IsHardcoreModeActive());
updateWindowTitle();
updateStatusBarWidgetVisibility();
updateDisplayWidgetCursor();
}
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)
{
// TODO: Find a better home for this.
if (InputManager::HasPointerAxisBinds())
{
relative = true;
hide_cursor = true;
}
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_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<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.DisplaySize.x = static_cast<float>(g_gpu_device->GetWindowWidth());
io.DisplaySize.y = static_cast<float>(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<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
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<u32>(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)

View file

@ -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();

View file

@ -174,6 +174,9 @@ static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis:
s_pointer_state;
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
// ------------------------------------------------------------------------
@ -699,16 +702,60 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
{
const Controller::ControllerBindingInfo& bi = cinfo->bindings[i];
const std::vector<std::string> 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<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;
}
}
@ -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<u32>(static_cast<u8>(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<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()
{
// 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()
@ -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<u32>(InputPointerAxis::X) && key.data <= static_cast<u32>(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)

View file

@ -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