mirror of https://github.com/PCSX2/pcsx2.git
Qt: Implement mouse->controller binding
Also implements forwarding mouse events to imgui.
This commit is contained in:
parent
7b3847cc5c
commit
470365644f
|
@ -33,7 +33,9 @@
|
|||
#include <QtGui/QWindowStateChangeEvent>
|
||||
#include <cmath>
|
||||
|
||||
#if !defined(_WIN32) && !defined(APPLE)
|
||||
#if defined(_WIN32)
|
||||
#include "common/RedtapeWindows.h"
|
||||
#elif !defined(APPLE)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
|
@ -50,7 +52,13 @@ DisplayWidget::DisplayWidget(QWidget* parent)
|
|||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
DisplayWidget::~DisplayWidget() = default;
|
||||
DisplayWidget::~DisplayWidget()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
ClipCursor(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
qreal DisplayWidget::devicePixelRatioFromScreen() const
|
||||
{
|
||||
|
@ -110,27 +118,96 @@ std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
|||
return wi;
|
||||
}
|
||||
|
||||
void DisplayWidget::setRelativeMode(bool enabled)
|
||||
void DisplayWidget::updateRelativeMode(bool master_enable)
|
||||
{
|
||||
if (m_relative_mouse_enabled == enabled)
|
||||
bool relative_mode = master_enable && InputManager::HasPointerAxisBinds();
|
||||
|
||||
#ifdef _WIN32
|
||||
// prefer ClipCursor() over warping movement when we're using raw input
|
||||
bool clip_cursor = relative_mode && false /*InputManager::IsUsingRawInput()*/;
|
||||
if (m_relative_mouse_enabled == relative_mode && m_clip_mouse_enabled == clip_cursor)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
m_relative_mouse_start_position = QCursor::pos();
|
||||
DevCon.WriteLn("updateRelativeMode(): relative=%s, clip=%s", relative_mode ? "yes" : "no", clip_cursor ? "yes" : "no");
|
||||
|
||||
const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2));
|
||||
QCursor::setPos(center_pos);
|
||||
m_relative_mouse_last_position = center_pos;
|
||||
if (!clip_cursor && m_clip_mouse_enabled)
|
||||
{
|
||||
m_clip_mouse_enabled = false;
|
||||
ClipCursor(nullptr);
|
||||
}
|
||||
#else
|
||||
if (m_relative_mouse_enabled == relative_mode)
|
||||
return;
|
||||
|
||||
DevCon.WriteLn("updateRelativeMode(): relative=%s", relative_mode ? "yes" : "no");
|
||||
#endif
|
||||
|
||||
if (relative_mode)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_relative_mouse_enabled = !clip_cursor;
|
||||
m_clip_mouse_enabled = clip_cursor;
|
||||
#else
|
||||
m_relative_mouse_enabled = true;
|
||||
#endif
|
||||
m_relative_mouse_start_pos = QCursor::pos();
|
||||
updateCenterPos();
|
||||
grabMouse();
|
||||
}
|
||||
else
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
QCursor::setPos(m_relative_mouse_start_position);
|
||||
m_relative_mouse_enabled = false;
|
||||
QCursor::setPos(m_relative_mouse_start_pos);
|
||||
releaseMouse();
|
||||
}
|
||||
|
||||
m_relative_mouse_enabled = enabled;
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCursor(bool master_enable)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled || m_clip_mouse_enabled);
|
||||
#else
|
||||
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled);
|
||||
#endif
|
||||
if (m_cursor_hidden == hide)
|
||||
return;
|
||||
|
||||
m_cursor_hidden = hide;
|
||||
if (hide)
|
||||
setCursor(Qt::BlankCursor);
|
||||
else
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCenterPos()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
{
|
||||
RECT rc;
|
||||
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
|
||||
ClipCursor(&rc);
|
||||
}
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
RECT rc;
|
||||
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
|
||||
{
|
||||
m_relative_mouse_center_pos.setX(((rc.right - rc.left) / 2) + rc.left);
|
||||
m_relative_mouse_center_pos.setY(((rc.bottom - rc.top) / 2) + rc.top);
|
||||
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (m_relative_mouse_enabled)
|
||||
{
|
||||
// we do a round trip here because these coordinates are dpi-unscaled
|
||||
m_relative_mouse_center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
|
||||
QCursor::setPos(m_relative_mouse_center_pos);
|
||||
m_relative_mouse_center_pos = QCursor::pos();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QPaintEngine* DisplayWidget::paintEngine() const
|
||||
|
@ -172,7 +249,10 @@ bool DisplayWidget::event(QEvent* event)
|
|||
m_keys_pressed_with_modifiers.push_back(key);
|
||||
}
|
||||
|
||||
emit windowKeyEvent(key, pressed);
|
||||
Host::RunOnCPUThread([key, pressed]() {
|
||||
InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), static_cast<float>(pressed));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -184,22 +264,39 @@ bool DisplayWidget::event(QEvent* event)
|
|||
{
|
||||
const qreal dpr = devicePixelRatioFromScreen();
|
||||
const QPoint mouse_pos = mouse_event->pos();
|
||||
const int scaled_x = static_cast<int>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
const int scaled_y = static_cast<int>(static_cast<qreal>(mouse_pos.y()) * dpr);
|
||||
|
||||
windowMouseMoveEvent(scaled_x, scaled_y);
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * dpr);
|
||||
InputManager::UpdatePointerAbsolutePosition(0, scaled_x, scaled_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
|
||||
const QPoint mouse_pos = mapToGlobal(mouse_event->pos());
|
||||
// On windows, we use winapi here. The reason being that the coordinates in QCursor
|
||||
// are un-dpi-scaled, so we lose precision at higher desktop scalings.
|
||||
float dx = 0.0f, dy = 0.0f;
|
||||
|
||||
const int dx = mouse_pos.x() - center_pos.x();
|
||||
const int dy = mouse_pos.y() - center_pos.y();
|
||||
m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx);
|
||||
m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy);
|
||||
windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y());
|
||||
QCursor::setPos(center_pos);
|
||||
#ifndef _WIN32
|
||||
const QPoint mouse_pos = QCursor::pos();
|
||||
if (mouse_pos != m_relative_mouse_center_pos)
|
||||
{
|
||||
dx = static_cast<float>(mouse_pos.x() - m_relative_mouse_center_pos.x());
|
||||
dy = static_cast<float>(mouse_pos.y() - m_relative_mouse_center_pos.y());
|
||||
QCursor::setPos(m_relative_mouse_center_pos);
|
||||
}
|
||||
#else
|
||||
POINT mouse_pos;
|
||||
if (GetCursorPos(&mouse_pos))
|
||||
{
|
||||
dx = static_cast<float>(mouse_pos.x - m_relative_mouse_center_pos.x());
|
||||
dy = static_cast<float>(mouse_pos.y - m_relative_mouse_center_pos.y());
|
||||
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dx != 0.0f)
|
||||
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::X, dx);
|
||||
if (dy != 0.0f)
|
||||
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::Y, dy);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -211,10 +308,16 @@ bool DisplayWidget::event(QEvent* event)
|
|||
{
|
||||
unsigned long button_index;
|
||||
if (_BitScanForward(&button_index, static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())))
|
||||
emit windowMouseButtonEvent(static_cast<int>(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
|
||||
{
|
||||
Host::RunOnCPUThread([button_index, pressed = (event->type() != QEvent::MouseButtonRelease)]() {
|
||||
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(0, button_index), static_cast<float>(pressed));
|
||||
});
|
||||
}
|
||||
|
||||
// don't toggle fullscreen when we're bound.. that wouldn't end well.
|
||||
if (event->type() == QEvent::MouseButtonDblClick &&
|
||||
static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton &&
|
||||
!InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) &&
|
||||
Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true))
|
||||
{
|
||||
g_emu_thread->toggleFullscreen();
|
||||
|
@ -225,8 +328,18 @@ bool DisplayWidget::event(QEvent* event)
|
|||
|
||||
case QEvent::Wheel:
|
||||
{
|
||||
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
|
||||
emit windowMouseWheelEvent(wheel_event->angleDelta());
|
||||
// wheel delta is 120 as in winapi
|
||||
const QPoint delta_angle(static_cast<QWheelEvent*>(event)->angleDelta());
|
||||
constexpr float DELTA = 120.0f;
|
||||
|
||||
const float dx = std::clamp(static_cast<float>(delta_angle.x()) / DELTA, -1.0f, 1.0f);
|
||||
if (dx != 0.0f)
|
||||
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::WheelX, dx);
|
||||
|
||||
const float dy = std::clamp(static_cast<float>(delta_angle.y()) / DELTA, -1.0f, 1.0f);
|
||||
if (dy != 0.0f)
|
||||
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::WheelY, dy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -249,6 +362,13 @@ bool DisplayWidget::event(QEvent* event)
|
|||
emit windowResizedEvent(scaled_width, scaled_height, dpr);
|
||||
}
|
||||
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Move:
|
||||
{
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,30 +30,37 @@ public:
|
|||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
__fi void setShouldHideCursor(bool hide) { m_should_hide_cursor = hide; }
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
qreal devicePixelRatioFromScreen() const;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
|
||||
void setRelativeMode(bool enabled);
|
||||
void updateRelativeMode(bool master_enable);
|
||||
void updateCursor(bool master_enable);
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowFocusEvent();
|
||||
void windowResizedEvent(int width, int height, float scale);
|
||||
void windowRestoredEvent();
|
||||
void windowKeyEvent(int key_code, bool pressed);
|
||||
void windowMouseMoveEvent(int x, int y);
|
||||
void windowMouseButtonEvent(int button, bool pressed);
|
||||
void windowMouseWheelEvent(const QPoint& angle_delta);
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
QPoint m_relative_mouse_start_position{};
|
||||
QPoint m_relative_mouse_last_position{};
|
||||
void updateCenterPos();
|
||||
|
||||
QPoint m_relative_mouse_start_pos{};
|
||||
QPoint m_relative_mouse_center_pos{};
|
||||
bool m_relative_mouse_enabled = false;
|
||||
#ifdef _WIN32
|
||||
bool m_clip_mouse_enabled = false;
|
||||
#endif
|
||||
bool m_should_hide_cursor = false;
|
||||
bool m_cursor_hidden = false;
|
||||
|
||||
std::vector<int> m_keys_pressed_with_modifiers;
|
||||
|
||||
u32 m_last_window_width = 0;
|
||||
|
|
|
@ -419,6 +419,8 @@ void EmuThread::loadOurSettings()
|
|||
|
||||
void EmuThread::checkForSettingChanges()
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
|
||||
|
||||
if (VMManager::HasValidVM())
|
||||
{
|
||||
const bool render_to_main = Host::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true);
|
||||
|
@ -585,29 +587,11 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget)
|
|||
connect(widget, &DisplayWidget::windowFocusEvent, this, &EmuThread::onDisplayWindowFocused);
|
||||
connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
|
||||
// connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
|
||||
connect(widget, &DisplayWidget::windowKeyEvent, this, &EmuThread::onDisplayWindowKeyEvent);
|
||||
connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &EmuThread::onDisplayWindowMouseMoveEvent);
|
||||
connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &EmuThread::onDisplayWindowMouseButtonEvent);
|
||||
connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent);
|
||||
}
|
||||
|
||||
void EmuThread::onDisplayWindowMouseMoveEvent(int x, int y) {}
|
||||
|
||||
void EmuThread::onDisplayWindowMouseButtonEvent(int button, bool pressed)
|
||||
{
|
||||
InputManager::InvokeEvents(InputManager::MakeHostMouseButtonKey(button), pressed ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle) {}
|
||||
|
||||
void EmuThread::onDisplayWindowKeyEvent(int key, bool pressed)
|
||||
{
|
||||
InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), pressed ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
|
||||
{
|
||||
if (!VMManager::HasValidVM())
|
||||
if (!s_host_display)
|
||||
return;
|
||||
|
||||
GetMTGS().ResizeDisplayWindow(width, height, scale);
|
||||
|
|
|
@ -142,12 +142,8 @@ private:
|
|||
private Q_SLOTS:
|
||||
void stopInThread();
|
||||
void doBackgroundControllerPoll();
|
||||
void onDisplayWindowMouseMoveEvent(int x, int y);
|
||||
void onDisplayWindowMouseButtonEvent(int button, bool pressed);
|
||||
void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
|
||||
void onDisplayWindowResized(int width, int height, float scale);
|
||||
void onDisplayWindowFocused();
|
||||
void onDisplayWindowKeyEvent(int key, bool pressed);
|
||||
|
||||
private:
|
||||
QThread* m_ui_thread;
|
||||
|
|
|
@ -766,6 +766,11 @@ bool MainWindow::isRenderingToMain() const
|
|||
return (m_display_widget && m_display_widget->parent() == this);
|
||||
}
|
||||
|
||||
bool MainWindow::shouldHideMouseCursor() const
|
||||
{
|
||||
return isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false);
|
||||
}
|
||||
|
||||
void MainWindow::switchToGameListView()
|
||||
{
|
||||
if (centralWidget() == m_game_list_widget)
|
||||
|
@ -897,6 +902,12 @@ void MainWindow::requestExit()
|
|||
close();
|
||||
}
|
||||
|
||||
void MainWindow::checkForSettingChanges()
|
||||
{
|
||||
if (m_display_widget)
|
||||
m_display_widget->updateRelativeMode(m_vm_valid && !m_vm_paused);
|
||||
}
|
||||
|
||||
void Host::InvalidateSaveStateCache()
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection);
|
||||
|
@ -1354,6 +1365,8 @@ void MainWindow::onVMPaused()
|
|||
updateWindowTitle();
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_status_fps_widget->setText(tr("Paused"));
|
||||
m_display_widget->updateRelativeMode(false);
|
||||
m_display_widget->updateCursor(false);
|
||||
}
|
||||
|
||||
void MainWindow::onVMResumed()
|
||||
|
@ -1369,6 +1382,8 @@ void MainWindow::onVMResumed()
|
|||
updateWindowTitle();
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_status_fps_widget->setText(m_last_fps_status);
|
||||
m_display_widget->updateRelativeMode(true);
|
||||
m_display_widget->updateCursor(true);
|
||||
if (m_display_widget)
|
||||
m_display_widget->setFocus();
|
||||
}
|
||||
|
@ -1381,8 +1396,17 @@ void MainWindow::onVMStopped()
|
|||
updateEmulationActions(false, false);
|
||||
updateWindowTitle();
|
||||
updateStatusBarWidgetVisibility();
|
||||
|
||||
if (m_display_widget)
|
||||
{
|
||||
m_display_widget->updateRelativeMode(false);
|
||||
m_display_widget->updateCursor(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
switchToGameListView();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc)
|
||||
{
|
||||
|
@ -1554,6 +1578,10 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
|
|||
updateWindowTitle();
|
||||
m_display_widget->setFocus();
|
||||
|
||||
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
|
||||
m_display_widget->updateRelativeMode(m_vm_valid && !m_vm_paused);
|
||||
m_display_widget->updateCursor(m_vm_valid && !m_vm_paused);
|
||||
|
||||
host_display->DoneRenderContextCurrent();
|
||||
return m_display_widget;
|
||||
}
|
||||
|
@ -1676,6 +1704,9 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, b
|
|||
|
||||
updateWindowTitle();
|
||||
m_display_widget->setFocus();
|
||||
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
|
||||
m_display_widget->updateRelativeMode(m_vm_valid && !m_vm_paused);
|
||||
m_display_widget->updateCursor(m_vm_valid && !m_vm_paused);
|
||||
|
||||
QSignalBlocker blocker(m_ui.actionFullscreen);
|
||||
m_ui.actionFullscreen->setChecked(fullscreen);
|
||||
|
|
|
@ -93,6 +93,7 @@ public Q_SLOTS:
|
|||
void runOnUIThread(const std::function<void()>& func);
|
||||
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool block_until_done = false);
|
||||
void requestExit();
|
||||
void checkForSettingChanges();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onUpdateCheckComplete();
|
||||
|
@ -184,6 +185,7 @@ private:
|
|||
bool isShowingGameList() const;
|
||||
bool isRenderingFullscreen() const;
|
||||
bool isRenderingToMain() const;
|
||||
bool shouldHideMouseCursor() const;
|
||||
void switchToGameListView();
|
||||
void switchToEmulationView();
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.pointerXInvert, "Pad", "PointerXInvert", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.pointerYInvert, "Pad", "PointerYInvert", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXScale, "Pad", "PointerXScale", 8.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYScale, "Pad", "PointerYScale", 8.0f);
|
||||
|
||||
if (dialog->isEditingProfile())
|
||||
{
|
||||
|
@ -56,6 +60,11 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
|||
for (QCheckBox* cb : {m_ui.multitapPort1, m_ui.multitapPort2})
|
||||
connect(cb, &QCheckBox::stateChanged, this, [this]() { emit bindingSetupChanged(); });
|
||||
|
||||
connect(m_ui.pointerXScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerYScale, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerXScale->value()));
|
||||
m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerYScale->value()));
|
||||
|
||||
updateSDLOptionsEnabled();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>500</height>
|
||||
<height>675</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -42,20 +42,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="enableSDLEnhancedMode">
|
||||
<property name="text">
|
||||
<string>DualShock 4 / DualSense Enhanced Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableSDLSource">
|
||||
<property name="text">
|
||||
<string>Enable SDL Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="enableSDLEnhancedMode">
|
||||
<property name="text">
|
||||
<string>DualShock 4 / DualSense Enhanced Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -86,6 +86,141 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Mouse/Pointer Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="2" column="1">
|
||||
<widget class="QSlider" name="pointerYScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QCheckBox" name="pointerYInvert">
|
||||
<property name="text">
|
||||
<string>Invert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="pointerXScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QCheckBox" name="pointerXInvert">
|
||||
<property name="text">
|
||||
<string>Invert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Vertical Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="pointerXScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="pointerYScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Horizontal Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Controller Multitap</string>
|
||||
|
@ -101,14 +236,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="multitapPort1">
|
||||
<property name="text">
|
||||
<string>Multitap on Console Port 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="multitapPort2">
|
||||
<property name="text">
|
||||
<string>Multitap on Console Port 2</string>
|
||||
|
@ -118,7 +253,7 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QGroupBox" name="profileSettings">
|
||||
<property name="title">
|
||||
<string>Profile Settings</string>
|
||||
|
@ -144,7 +279,7 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
@ -157,7 +292,7 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="5">
|
||||
<item row="0" column="1" rowspan="6">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Detected Devices</string>
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace ControllerSettingWidgetBinder
|
|||
if (sif)
|
||||
{
|
||||
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setBoolValue(widget, value);
|
||||
Accessor::setFloatValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
|
||||
const float new_value = Accessor::getFloatValue(widget);
|
||||
|
@ -90,7 +90,7 @@ namespace ControllerSettingWidgetBinder
|
|||
else
|
||||
{
|
||||
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
|
||||
Accessor::setBoolValue(widget, value);
|
||||
Accessor::setFloatValue(widget, value);
|
||||
|
||||
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
|
||||
const float new_value = Accessor::getFloatValue(widget);
|
||||
|
|
|
@ -55,7 +55,7 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
|
|||
const QEvent::Type event_type = event->type();
|
||||
|
||||
// if the key is being released, set the input
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonPress)
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonRelease)
|
||||
{
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
|
@ -67,18 +67,44 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event)
|
|||
m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(key_event->key()));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonPress)
|
||||
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
// double clicks get triggered if we click bind, then click again quickly.
|
||||
unsigned long button_index;
|
||||
if (_BitScanForward(&button_index, static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())))
|
||||
m_new_bindings.push_back(InputManager::MakeHostMouseButtonKey(button_index));
|
||||
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonDblClick)
|
||||
else if (event_type == QEvent::MouseMove)
|
||||
{
|
||||
// just eat double clicks
|
||||
// if we've moved more than a decent distance from the center of the widget, bind it.
|
||||
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
|
||||
static constexpr const s32 THRESHOLD = 50;
|
||||
const QPoint diff(static_cast<QMouseEvent*>(event)->globalPos() - m_input_listen_start_position);
|
||||
bool has_one = false;
|
||||
|
||||
if (std::abs(diff.x()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
|
||||
key.negative = (diff.x() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
if (std::abs(diff.y()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
|
||||
key.negative = (diff.y() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
|
||||
if (has_one)
|
||||
{
|
||||
addNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -98,6 +124,7 @@ void InputBindingDialog::onInputListenTimerTimeout()
|
|||
void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
m_new_bindings.clear();
|
||||
m_input_listen_start_position = QCursor::pos();
|
||||
m_input_listen_timer = new QTimer(this);
|
||||
m_input_listen_timer->setSingleShot(false);
|
||||
m_input_listen_timer->start(1000);
|
||||
|
@ -114,6 +141,7 @@ void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
|
|||
installEventFilter(this);
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
setMouseTracking(true);
|
||||
hookInputManager();
|
||||
}
|
||||
|
||||
|
@ -131,6 +159,7 @@ void InputBindingDialog::stopListeningForInput()
|
|||
unhookInputManager();
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
setMouseTracking(false);
|
||||
removeEventFilter(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,4 +68,5 @@ protected:
|
|||
|
||||
QTimer* m_input_listen_timer = nullptr;
|
||||
u32 m_input_listen_remaining_seconds = 0;
|
||||
QPoint m_input_listen_start_position{};
|
||||
};
|
||||
|
|
|
@ -106,7 +106,7 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
|||
const QEvent::Type event_type = event->type();
|
||||
|
||||
// if the key is being released, set the input
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonPress)
|
||||
if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonRelease)
|
||||
{
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
|
@ -118,18 +118,44 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event)
|
|||
m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(key_event->key()));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonPress)
|
||||
else if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
// double clicks get triggered if we click bind, then click again quickly.
|
||||
unsigned long button_index;
|
||||
if (_BitScanForward(&button_index, static_cast<u32>(static_cast<const QMouseEvent*>(event)->button())))
|
||||
m_new_bindings.push_back(InputManager::MakeHostMouseButtonKey(button_index));
|
||||
m_new_bindings.push_back(InputManager::MakePointerButtonKey(0, button_index));
|
||||
return true;
|
||||
}
|
||||
else if (event_type == QEvent::MouseButtonDblClick)
|
||||
else if (event_type == QEvent::MouseMove)
|
||||
{
|
||||
// just eat double clicks
|
||||
// if we've moved more than a decent distance from the center of the widget, bind it.
|
||||
// this is so we don't accidentally bind to the mouse if you bump it while reaching for your pad.
|
||||
static constexpr const s32 THRESHOLD = 50;
|
||||
const QPoint diff(static_cast<QMouseEvent*>(event)->globalPos() - m_input_listen_start_position);
|
||||
bool has_one = false;
|
||||
|
||||
if (std::abs(diff.x()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::X));
|
||||
key.negative = (diff.x() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
if (std::abs(diff.y()) >= THRESHOLD)
|
||||
{
|
||||
InputBindingKey key(InputManager::MakePointerAxisKey(0, InputPointerAxis::Y));
|
||||
key.negative = (diff.y() < 0);
|
||||
m_new_bindings.push_back(key);
|
||||
has_one = true;
|
||||
}
|
||||
|
||||
if (has_one)
|
||||
{
|
||||
setNewBinding();
|
||||
stopListeningForInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -240,6 +266,7 @@ void InputBindingWidget::onInputListenTimerTimeout()
|
|||
void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
||||
{
|
||||
m_new_bindings.clear();
|
||||
m_input_listen_start_position = QCursor::pos();
|
||||
m_input_listen_timer = new QTimer(this);
|
||||
m_input_listen_timer->setSingleShot(false);
|
||||
m_input_listen_timer->start(1000);
|
||||
|
@ -252,6 +279,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
|
|||
installEventFilter(this);
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
setMouseTracking(true);
|
||||
hookInputManager();
|
||||
}
|
||||
|
||||
|
@ -263,6 +291,7 @@ void InputBindingWidget::stopListeningForInput()
|
|||
std::vector<InputBindingKey>().swap(m_new_bindings);
|
||||
|
||||
unhookInputManager();
|
||||
setMouseTracking(false);
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
removeEventFilter(this);
|
||||
|
|
|
@ -73,6 +73,7 @@ protected:
|
|||
std::vector<InputBindingKey> m_new_bindings;
|
||||
QTimer* m_input_listen_timer = nullptr;
|
||||
u32 m_input_listen_remaining_seconds = 0;
|
||||
QPoint m_input_listen_start_position{};
|
||||
};
|
||||
|
||||
class InputVibrationBindingWidget : public QPushButton
|
||||
|
|
|
@ -117,7 +117,6 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
|
|||
m_ui.pauseOnStart->setDisabled(true);
|
||||
m_ui.pauseOnFocusLoss->setDisabled(true);
|
||||
m_ui.disableWindowResizing->setDisabled(true);
|
||||
m_ui.hideMouseCursor->setDisabled(true);
|
||||
m_ui.language->setDisabled(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="hideMouseCursor">
|
||||
<property name="text">
|
||||
<string>Hide Mouse Cursor</string>
|
||||
<string>Hide Cursor In Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -19,15 +19,18 @@
|
|||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include "Config.h"
|
||||
#include "Counters.h"
|
||||
#include "Frontend/ImGuiManager.h"
|
||||
#include "Frontend/InputManager.h"
|
||||
#include "GS.h"
|
||||
#include "GS/GS.h"
|
||||
#include "Host.h"
|
||||
|
@ -39,10 +42,21 @@
|
|||
#include "VMManager.h"
|
||||
#endif
|
||||
|
||||
static void SetImGuiStyle();
|
||||
namespace ImGuiManager
|
||||
{
|
||||
static void SetStyle();
|
||||
static void SetKeyMap();
|
||||
static bool LoadFontData();
|
||||
static void UnloadFontData();
|
||||
static bool AddImGuiFonts();
|
||||
static ImFont* AddTextFont(float size);
|
||||
static ImFont* AddFixedFont(float size);
|
||||
static bool AddIconFonts(float size);
|
||||
static void AcquirePendingOSDMessages();
|
||||
static void DrawOSDMessages();
|
||||
static void FormatProcessorStat(std::string& text, double usage, double time);
|
||||
static void DrawPerformanceOverlay();
|
||||
} // namespace ImGuiManager
|
||||
|
||||
static float s_global_scale = 1.0f;
|
||||
|
||||
|
@ -53,6 +67,17 @@ 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 Common::Timer s_last_render_time;
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
// cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events
|
||||
static std::atomic_bool s_imgui_wants_keyboard{false};
|
||||
static std::atomic_bool s_imgui_wants_mouse{false};
|
||||
|
||||
// mapping of host key -> imgui key
|
||||
static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
|
||||
#endif
|
||||
|
||||
bool ImGuiManager::Initialize()
|
||||
{
|
||||
if (!LoadFontData())
|
||||
|
@ -62,23 +87,30 @@ bool ImGuiManager::Initialize()
|
|||
}
|
||||
|
||||
HostDisplay* display = Host::GetHostDisplay();
|
||||
|
||||
ImGui::CreateContext();
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
|
||||
s_global_scale = std::max(1.0f, display->GetWindowScale() * static_cast<float>(EmuConfig.GS.OsdScale / 100.0));
|
||||
|
||||
ImGui::GetIO().DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
|
||||
ImGui::GetIO().DisplaySize.x = static_cast<float>(display->GetWindowWidth());
|
||||
ImGui::GetIO().DisplaySize.y = static_cast<float>(display->GetWindowHeight());
|
||||
ImGui::GetStyle() = ImGuiStyle();
|
||||
ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f);
|
||||
SetImGuiStyle();
|
||||
ImGui::GetStyle().ScaleAllSizes(s_global_scale);
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr;
|
||||
#ifdef PCSX2_CORE
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
io.BackendUsingLegacyKeyArrays = 0;
|
||||
io.BackendUsingLegacyNavInputArray = 0;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
|
||||
#endif
|
||||
|
||||
io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
|
||||
io.DisplaySize.x = static_cast<float>(display->GetWindowWidth());
|
||||
io.DisplaySize.y = static_cast<float>(display->GetWindowHeight());
|
||||
|
||||
SetKeyMap();
|
||||
SetStyle();
|
||||
|
||||
if (!display->CreateImGuiContext())
|
||||
{
|
||||
pxFailRel("Failed to create ImGui device context");
|
||||
display->DestroyImGuiContext();
|
||||
ImGui::DestroyContext();
|
||||
UnloadFontData();
|
||||
return false;
|
||||
|
@ -93,6 +125,9 @@ bool ImGuiManager::Initialize()
|
|||
return false;
|
||||
}
|
||||
|
||||
// don't need the font data anymore, save some memory
|
||||
ImGui::GetIO().Fonts->ClearTexData();
|
||||
|
||||
NewFrame();
|
||||
return true;
|
||||
}
|
||||
|
@ -104,6 +139,10 @@ void ImGuiManager::Shutdown()
|
|||
display->DestroyImGuiContext();
|
||||
if (ImGui::GetCurrentContext())
|
||||
ImGui::DestroyContext();
|
||||
|
||||
s_standard_font = nullptr;
|
||||
s_fixed_font = nullptr;
|
||||
|
||||
UnloadFontData();
|
||||
}
|
||||
|
||||
|
@ -139,7 +178,7 @@ void ImGuiManager::UpdateScale()
|
|||
|
||||
ImGui::GetStyle() = ImGuiStyle();
|
||||
ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f);
|
||||
SetImGuiStyle();
|
||||
SetStyle();
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
|
||||
if (!AddImGuiFonts())
|
||||
|
@ -153,14 +192,24 @@ void ImGuiManager::UpdateScale()
|
|||
|
||||
void ImGuiManager::NewFrame()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DeltaTime = s_last_render_time.GetTimeSecondsAndReset();
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
s_imgui_wants_keyboard.store(io.WantCaptureKeyboard, std::memory_order_relaxed);
|
||||
s_imgui_wants_mouse.store(io.WantCaptureMouse, std::memory_order_release);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetImGuiStyle()
|
||||
void ImGuiManager::SetStyle()
|
||||
{
|
||||
ImGuiStyle* style = &ImGui::GetStyle();
|
||||
ImVec4* colors = style->Colors;
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
style = ImGuiStyle();
|
||||
style.WindowMinSize = ImVec2(1.0f, 1.0f);
|
||||
|
||||
ImVec4* colors = style.Colors;
|
||||
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
|
||||
|
@ -209,9 +258,58 @@ void SetImGuiStyle()
|
|||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
|
||||
style.ScaleAllSizes(s_global_scale);
|
||||
}
|
||||
|
||||
bool LoadFontData()
|
||||
void ImGuiManager::SetKeyMap()
|
||||
{
|
||||
#ifdef PCSX2_CORE
|
||||
struct KeyMapping
|
||||
{
|
||||
int index;
|
||||
const char* name;
|
||||
const char* alt_name;
|
||||
};
|
||||
|
||||
static constexpr KeyMapping mapping[] = {{ImGuiKey_LeftArrow, "Left"}, {ImGuiKey_RightArrow, "Right"}, {ImGuiKey_UpArrow, "Up"},
|
||||
{ImGuiKey_DownArrow, "Down"}, {ImGuiKey_PageUp, "PageUp"}, {ImGuiKey_PageDown, "PageDown"}, {ImGuiKey_Home, "Home"},
|
||||
{ImGuiKey_End, "End"}, {ImGuiKey_Insert, "Insert"}, {ImGuiKey_Delete, "Delete"}, {ImGuiKey_Backspace, "Backspace"},
|
||||
{ImGuiKey_Space, "Space"}, {ImGuiKey_Enter, "Return"}, {ImGuiKey_Escape, "Escape"}, {ImGuiKey_LeftCtrl, "LeftCtrl", "Ctrl"},
|
||||
{ImGuiKey_LeftShift, "LeftShift", "Shift"}, {ImGuiKey_LeftAlt, "LeftAlt", "Alt"}, {ImGuiKey_LeftSuper, "LeftSuper", "Super"},
|
||||
{ImGuiKey_RightCtrl, "RightCtrl"}, {ImGuiKey_RightShift, "RightShift"}, {ImGuiKey_RightAlt, "RightAlt"},
|
||||
{ImGuiKey_RightSuper, "RightSuper"}, {ImGuiKey_Menu, "Menu"}, {ImGuiKey_0, "0"}, {ImGuiKey_1, "1"}, {ImGuiKey_2, "2"},
|
||||
{ImGuiKey_3, "3"}, {ImGuiKey_4, "4"}, {ImGuiKey_5, "5"}, {ImGuiKey_6, "6"}, {ImGuiKey_7, "7"}, {ImGuiKey_8, "8"}, {ImGuiKey_9, "9"},
|
||||
{ImGuiKey_A, "A"}, {ImGuiKey_B, "B"}, {ImGuiKey_C, "C"}, {ImGuiKey_D, "D"}, {ImGuiKey_E, "E"}, {ImGuiKey_F, "F"}, {ImGuiKey_G, "G"},
|
||||
{ImGuiKey_H, "H"}, {ImGuiKey_I, "I"}, {ImGuiKey_J, "J"}, {ImGuiKey_K, "K"}, {ImGuiKey_L, "L"}, {ImGuiKey_M, "M"}, {ImGuiKey_N, "N"},
|
||||
{ImGuiKey_O, "O"}, {ImGuiKey_P, "P"}, {ImGuiKey_Q, "Q"}, {ImGuiKey_R, "R"}, {ImGuiKey_S, "S"}, {ImGuiKey_T, "T"}, {ImGuiKey_U, "U"},
|
||||
{ImGuiKey_V, "V"}, {ImGuiKey_W, "W"}, {ImGuiKey_X, "X"}, {ImGuiKey_Y, "Y"}, {ImGuiKey_Z, "Z"}, {ImGuiKey_F1, "F1"},
|
||||
{ImGuiKey_F2, "F2"}, {ImGuiKey_F3, "F3"}, {ImGuiKey_F4, "F4"}, {ImGuiKey_F5, "F5"}, {ImGuiKey_F6, "F6"}, {ImGuiKey_F7, "F7"},
|
||||
{ImGuiKey_F8, "F8"}, {ImGuiKey_F9, "F9"}, {ImGuiKey_F10, "F10"}, {ImGuiKey_F11, "F11"}, {ImGuiKey_F12, "F12"},
|
||||
{ImGuiKey_Apostrophe, "Apostrophe"}, {ImGuiKey_Comma, "Comma"}, {ImGuiKey_Minus, "Minus"}, {ImGuiKey_Period, "Period"},
|
||||
{ImGuiKey_Slash, "Slash"}, {ImGuiKey_Semicolon, "Semicolon"}, {ImGuiKey_Equal, "Equal"}, {ImGuiKey_LeftBracket, "BracketLeft"},
|
||||
{ImGuiKey_Backslash, "Backslash"}, {ImGuiKey_RightBracket, "BracketRight"}, {ImGuiKey_GraveAccent, "QuoteLeft"},
|
||||
{ImGuiKey_CapsLock, "CapsLock"}, {ImGuiKey_ScrollLock, "ScrollLock"}, {ImGuiKey_NumLock, "NumLock"},
|
||||
{ImGuiKey_PrintScreen, "PrintScreen"}, {ImGuiKey_Pause, "Pause"}, {ImGuiKey_Keypad0, "Keypad0"}, {ImGuiKey_Keypad1, "Keypad1"},
|
||||
{ImGuiKey_Keypad2, "Keypad2"}, {ImGuiKey_Keypad3, "Keypad3"}, {ImGuiKey_Keypad4, "Keypad4"}, {ImGuiKey_Keypad5, "Keypad5"},
|
||||
{ImGuiKey_Keypad6, "Keypad6"}, {ImGuiKey_Keypad7, "Keypad7"}, {ImGuiKey_Keypad8, "Keypad8"}, {ImGuiKey_Keypad9, "Keypad9"},
|
||||
{ImGuiKey_KeypadDecimal, "KeypadPeriod"}, {ImGuiKey_KeypadDivide, "KeypadDivide"}, {ImGuiKey_KeypadMultiply, "KeypadMultiply"},
|
||||
{ImGuiKey_KeypadSubtract, "KeypadMinus"}, {ImGuiKey_KeypadAdd, "KeypadPlus"}, {ImGuiKey_KeypadEnter, "KeypadReturn"},
|
||||
{ImGuiKey_KeypadEqual, "KeypadEqual"}};
|
||||
|
||||
s_imgui_key_map.clear();
|
||||
for (const KeyMapping& km : mapping)
|
||||
{
|
||||
std::optional<u32> map(InputManager::ConvertHostKeyboardStringToCode(km.name));
|
||||
if (!map.has_value() && km.alt_name)
|
||||
map = InputManager::ConvertHostKeyboardStringToCode(km.alt_name);
|
||||
if (map.has_value())
|
||||
s_imgui_key_map[map.value()] = km.index;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ImGuiManager::LoadFontData()
|
||||
{
|
||||
if (s_standard_font_data.empty())
|
||||
{
|
||||
|
@ -243,14 +341,14 @@ bool LoadFontData()
|
|||
return true;
|
||||
}
|
||||
|
||||
void UnloadFontData()
|
||||
void ImGuiManager::UnloadFontData()
|
||||
{
|
||||
std::vector<u8>().swap(s_standard_font_data);
|
||||
std::vector<u8>().swap(s_fixed_font_data);
|
||||
std::vector<u8>().swap(s_icon_font_data);
|
||||
}
|
||||
|
||||
static ImFont* AddTextFont(float size /*= 15.0f*/)
|
||||
ImFont* ImGuiManager::AddTextFont(float size)
|
||||
{
|
||||
static const ImWchar default_ranges[] = {
|
||||
// Basic Latin + Latin Supplement + Central European diacritics
|
||||
|
@ -278,15 +376,15 @@ static ImFont* AddTextFont(float size /*= 15.0f*/)
|
|||
s_standard_font_data.data(), static_cast<int>(s_standard_font_data.size()), size, &cfg, default_ranges);
|
||||
}
|
||||
|
||||
static ImFont* AddFixedFont(float size)
|
||||
ImFont* ImGuiManager::AddFixedFont(float size)
|
||||
{
|
||||
ImFontConfig cfg;
|
||||
cfg.FontDataOwnedByAtlas = false;
|
||||
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_fixed_font_data.data(),
|
||||
static_cast<int>(s_fixed_font_data.size()), size, &cfg, nullptr);
|
||||
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
|
||||
s_fixed_font_data.data(), static_cast<int>(s_fixed_font_data.size()), size, &cfg, nullptr);
|
||||
}
|
||||
|
||||
static bool AddIconFonts(float size)
|
||||
bool ImGuiManager::AddIconFonts(float size)
|
||||
{
|
||||
static const ImWchar range_fa[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
|
||||
|
||||
|
@ -297,11 +395,11 @@ static bool AddIconFonts(float size)
|
|||
cfg.GlyphMaxAdvanceX = size * 0.75f;
|
||||
cfg.FontDataOwnedByAtlas = false;
|
||||
|
||||
return (ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_icon_font_data.data(), static_cast<int>(s_icon_font_data.size()),
|
||||
size * 0.75f, &cfg, range_fa) != nullptr);
|
||||
return (ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
|
||||
s_icon_font_data.data(), static_cast<int>(s_icon_font_data.size()), size * 0.75f, &cfg, range_fa) != nullptr);
|
||||
}
|
||||
|
||||
bool AddImGuiFonts()
|
||||
bool ImGuiManager::AddImGuiFonts()
|
||||
{
|
||||
const float standard_font_size = std::ceil(15.0f * s_global_scale);
|
||||
|
||||
|
@ -387,7 +485,7 @@ void Host::ClearOSDMessages()
|
|||
s_osd_active_messages.clear();
|
||||
}
|
||||
|
||||
static void AcquirePendingOSDMessages()
|
||||
void ImGuiManager::AcquirePendingOSDMessages()
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order_consume);
|
||||
if (s_osd_posted_messages.empty())
|
||||
|
@ -403,10 +501,9 @@ static void AcquirePendingOSDMessages()
|
|||
{
|
||||
OSDMessage& new_msg = s_osd_posted_messages.front();
|
||||
std::deque<OSDMessage>::iterator iter;
|
||||
if (!new_msg.key.empty() && (iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(),
|
||||
[&new_msg](const OSDMessage& other) {
|
||||
return new_msg.key == other.key;
|
||||
})) != s_osd_active_messages.end())
|
||||
if (!new_msg.key.empty() &&
|
||||
(iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(),
|
||||
[&new_msg](const OSDMessage& other) { return new_msg.key == other.key; })) != s_osd_active_messages.end())
|
||||
{
|
||||
iter->text = std::move(new_msg.text);
|
||||
iter->duration = new_msg.duration;
|
||||
|
@ -426,7 +523,7 @@ static void AcquirePendingOSDMessages()
|
|||
}
|
||||
}
|
||||
|
||||
static void DrawOSDMessages()
|
||||
void ImGuiManager::DrawOSDMessages()
|
||||
{
|
||||
ImFont* const font = ImGui::GetFont();
|
||||
const float scale = s_global_scale;
|
||||
|
@ -469,13 +566,13 @@ static void DrawOSDMessages()
|
|||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x21, 0x21, 0x21, alpha), rounding);
|
||||
dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, alpha), rounding);
|
||||
dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, alpha),
|
||||
msg.text.c_str(), msg.text.c_str() + msg.text.length(), max_width, &text_rect);
|
||||
dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, alpha), msg.text.c_str(),
|
||||
msg.text.c_str() + msg.text.length(), max_width, &text_rect);
|
||||
position_y += size.y + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
static void FormatProcessorStat(std::string& text, double usage, double time)
|
||||
void ImGuiManager::FormatProcessorStat(std::string& text, double usage, double time)
|
||||
{
|
||||
// Some values, such as GPU (and even CPU to some extent) can be out of phase with the wall clock,
|
||||
// which the processor time is divided by to get a utilization percentage. Let's clamp it at 100%,
|
||||
|
@ -486,7 +583,7 @@ static void FormatProcessorStat(std::string& text, double usage, double time)
|
|||
fmt::format_to(std::back_inserter(text), "{:.1f}% ({:.2f}ms)", usage, time);
|
||||
}
|
||||
|
||||
static void DrawPerformanceOverlay()
|
||||
void ImGuiManager::DrawPerformanceOverlay()
|
||||
{
|
||||
const float scale = s_global_scale;
|
||||
const float shadow_offset = std::ceil(1.0f * scale);
|
||||
|
@ -504,14 +601,11 @@ static void DrawPerformanceOverlay()
|
|||
#define DRAW_LINE(font, text, color) \
|
||||
do \
|
||||
{ \
|
||||
text_size = \
|
||||
font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, (text), nullptr, nullptr); \
|
||||
dl->AddText( \
|
||||
font, font->FontSize, \
|
||||
text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, (text), nullptr, nullptr); \
|
||||
dl->AddText(font, font->FontSize, \
|
||||
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset), \
|
||||
IM_COL32(0, 0, 0, 100), (text)); \
|
||||
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y), color, \
|
||||
(text)); \
|
||||
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y), color, (text)); \
|
||||
position_y += text_size.y + spacing; \
|
||||
} while (0)
|
||||
|
||||
|
@ -529,11 +623,13 @@ static void DrawPerformanceOverlay()
|
|||
switch (PerformanceMetrics::GetInternalFPSMethod())
|
||||
{
|
||||
case PerformanceMetrics::InternalFPSMethod::GSPrivilegedRegister:
|
||||
fmt::format_to(std::back_inserter(text), "G: {:.2f} [P] | V: {:.2f}", PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetFPS());
|
||||
fmt::format_to(std::back_inserter(text), "G: {:.2f} [P] | V: {:.2f}", PerformanceMetrics::GetInternalFPS(),
|
||||
PerformanceMetrics::GetFPS());
|
||||
break;
|
||||
|
||||
case PerformanceMetrics::InternalFPSMethod::DISPFBBlit:
|
||||
fmt::format_to(std::back_inserter(text), "G: {:.2f} [B] | V: {:.2f}", PerformanceMetrics::GetInternalFPS(), PerformanceMetrics::GetFPS());
|
||||
fmt::format_to(std::back_inserter(text), "G: {:.2f} [B] | V: {:.2f}", PerformanceMetrics::GetInternalFPS(),
|
||||
PerformanceMetrics::GetFPS());
|
||||
break;
|
||||
|
||||
case PerformanceMetrics::InternalFPSMethod::None:
|
||||
|
@ -651,6 +747,9 @@ static void DrawPerformanceOverlay()
|
|||
|
||||
void ImGuiManager::RenderOSD()
|
||||
{
|
||||
// acquire for IO.MousePos.
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
DrawPerformanceOverlay();
|
||||
|
||||
AcquirePendingOSDMessages();
|
||||
|
@ -671,3 +770,96 @@ ImFont* ImGuiManager::GetFixedFont()
|
|||
{
|
||||
return s_fixed_font;
|
||||
}
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
|
||||
void ImGuiManager::UpdateMousePosition(float x, float y)
|
||||
{
|
||||
if (!ImGui::GetCurrentContext())
|
||||
return;
|
||||
|
||||
ImGui::GetIO().MousePos = ImVec2(x, y);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
}
|
||||
|
||||
bool ImGuiManager::ProcessPointerButtonEvent(InputBindingKey key, float value)
|
||||
{
|
||||
if (!ImGui::GetCurrentContext() || key.data >= std::size(ImGui::GetIO().MouseDown))
|
||||
return false;
|
||||
|
||||
// still update state anyway
|
||||
GetMTGS().RunOnGSThread([button = key.data, down = (value != 0.0f)]() { ImGui::GetIO().AddMouseButtonEvent(button, down); });
|
||||
|
||||
return s_imgui_wants_mouse.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value)
|
||||
{
|
||||
if (!ImGui::GetCurrentContext() || value == 0.0f || key.data < static_cast<u32>(InputPointerAxis::WheelX))
|
||||
return false;
|
||||
|
||||
// still update state anyway
|
||||
const bool horizontal = (key.data == static_cast<u32>(InputPointerAxis::WheelX));
|
||||
GetMTGS().RunOnGSThread([wheel_x = horizontal ? value : 0.0f, wheel_y = horizontal ? 0.0f : value]() {
|
||||
ImGui::GetIO().AddMouseWheelEvent(wheel_x, wheel_y);
|
||||
});
|
||||
|
||||
return s_imgui_wants_mouse.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value)
|
||||
{
|
||||
decltype(s_imgui_key_map)::iterator iter;
|
||||
if (!ImGui::GetCurrentContext() || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end())
|
||||
return false;
|
||||
|
||||
// still update state anyway
|
||||
GetMTGS().RunOnGSThread([imkey = iter->second, down = (value != 0.0f)]() { ImGui::GetIO().AddKeyEvent(imkey, down); });
|
||||
|
||||
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value)
|
||||
{
|
||||
static constexpr ImGuiKey key_map[] = {
|
||||
ImGuiKey_None, // Unknown,
|
||||
ImGuiKey_GamepadDpadUp, // DPadUp
|
||||
ImGuiKey_GamepadDpadRight, // DPadRight
|
||||
ImGuiKey_GamepadDpadLeft, // DPadLeft
|
||||
ImGuiKey_GamepadDpadDown, // DPadDown
|
||||
ImGuiKey_None, // LeftStickUp
|
||||
ImGuiKey_None, // LeftStickRight
|
||||
ImGuiKey_None, // LeftStickDown
|
||||
ImGuiKey_None, // LeftStickLeft
|
||||
ImGuiKey_GamepadL3, // L3
|
||||
ImGuiKey_None, // RightStickUp
|
||||
ImGuiKey_None, // RightStickRight
|
||||
ImGuiKey_None, // RightStickDown
|
||||
ImGuiKey_None, // RightStickLeft
|
||||
ImGuiKey_GamepadR3, // R3
|
||||
ImGuiKey_GamepadFaceUp, // Triangle
|
||||
ImGuiKey_GamepadFaceRight, // Circle
|
||||
ImGuiKey_GamepadFaceDown, // Cross
|
||||
ImGuiKey_GamepadFaceLeft, // Square
|
||||
ImGuiKey_GamepadBack, // Select
|
||||
ImGuiKey_GamepadStart, // Start
|
||||
ImGuiKey_None, // System
|
||||
ImGuiKey_GamepadL1, // L1
|
||||
ImGuiKey_GamepadL2, // L2
|
||||
ImGuiKey_GamepadR1, // R1
|
||||
ImGuiKey_GamepadL2, // R2
|
||||
};
|
||||
|
||||
if (!ImGui::GetCurrentContext() || !s_imgui_wants_keyboard.load(std::memory_order_acquire))
|
||||
return false;
|
||||
|
||||
if (static_cast<u32>(key) >= std::size(key_map) || key_map[static_cast<u32>(key)] == ImGuiKey_None)
|
||||
return false;
|
||||
|
||||
GetMTGS().RunOnGSThread(
|
||||
[key = key_map[static_cast<u32>(key)], value]() { ImGui::GetIO().AddKeyAnalogEvent(key, (value > 0.0f), value); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // PCSX2_CORE
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
struct ImFont;
|
||||
|
||||
union InputBindingKey;
|
||||
enum class GenericInputBinding : u8;
|
||||
|
||||
namespace ImGuiManager
|
||||
{
|
||||
/// Initializes ImGui, creates fonts, etc.
|
||||
|
@ -45,5 +48,25 @@ namespace ImGuiManager
|
|||
|
||||
/// Returns the fixed-width font for external drawing.
|
||||
ImFont* GetFixedFont();
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
/// Called on the UI or CPU thread in response to mouse movement.
|
||||
void UpdateMousePosition(float x, float y);
|
||||
|
||||
/// Called on the CPU thread in response to a mouse button press.
|
||||
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
|
||||
bool ProcessPointerButtonEvent(InputBindingKey key, float value);
|
||||
|
||||
/// Called on the CPU thread in response to a mouse wheel movement.
|
||||
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
|
||||
bool ProcessPointerAxisEvent(InputBindingKey key, float value);
|
||||
|
||||
/// Called on the CPU thread in response to a key press.
|
||||
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
|
||||
bool ProcessHostKeyEvent(InputBindingKey key, float value);
|
||||
|
||||
/// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation.
|
||||
bool ProcessGenericInputEvent(GenericInputBinding key, float value);
|
||||
#endif
|
||||
} // namespace ImGuiManager
|
||||
|
||||
|
|
|
@ -16,10 +16,14 @@
|
|||
#include "PrecompiledHeader.h"
|
||||
#include "Frontend/InputManager.h"
|
||||
#include "Frontend/InputSource.h"
|
||||
#include "Frontend/ImGuiManager.h"
|
||||
#include "PAD/Host/PAD.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
#include "VMManager.h"
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
@ -36,7 +40,7 @@ enum : u32
|
|||
{
|
||||
MAX_KEYS_PER_BINDING = 4,
|
||||
MAX_MOTORS_PER_PAD = 2,
|
||||
FIRST_EXTERNAL_INPUT_SOURCE = static_cast<u32>(InputSourceType::Mouse) + 1u,
|
||||
FIRST_EXTERNAL_INPUT_SOURCE = static_cast<u32>(InputSourceType::Pointer) + 1u,
|
||||
LAST_EXTERNAL_INPUT_SOURCE = static_cast<u32>(InputSourceType::Count),
|
||||
};
|
||||
|
||||
|
@ -90,10 +94,8 @@ struct PadVibrationBinding
|
|||
// ------------------------------------------------------------------------
|
||||
namespace InputManager
|
||||
{
|
||||
static std::optional<InputBindingKey> ParseHostKeyboardKey(
|
||||
const std::string_view& source, const std::string_view& sub_binding);
|
||||
static std::optional<InputBindingKey> ParseHostMouseKey(
|
||||
const std::string_view& source, const std::string_view& sub_binding);
|
||||
static std::optional<InputBindingKey> ParseHostKeyboardKey(const std::string_view& source, const std::string_view& sub_binding);
|
||||
static std::optional<InputBindingKey> ParsePointerKey(const std::string_view& source, const std::string_view& sub_binding);
|
||||
|
||||
static std::vector<std::string_view> SplitChord(const std::string_view& binding);
|
||||
static bool SplitBinding(const std::string_view& binding, std::string_view* source, std::string_view* sub_binding);
|
||||
|
@ -105,8 +107,10 @@ namespace InputManager
|
|||
static void AddHotkeyBindings(SettingsInterface& si);
|
||||
static void AddPadBindings(SettingsInterface& si, u32 pad, const char* default_type);
|
||||
static void UpdateContinuedVibration();
|
||||
static void GenerateRelativeMouseEvents();
|
||||
|
||||
static bool DoEventHook(InputBindingKey key, float value);
|
||||
static bool PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key);
|
||||
} // namespace InputManager
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -132,6 +136,24 @@ static std::array<std::unique_ptr<InputSource>, static_cast<u32>(InputSourceType
|
|||
// ------------------------------------------------------------------------
|
||||
static const HotkeyInfo* const s_hotkey_list[] = {g_vm_manager_hotkeys, g_gs_hotkeys, g_host_hotkeys};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Tracking host mouse movement and turning into relative events
|
||||
// 4 axes: pointer left/right, wheel vertical/horizontal. Last/Next/Normalized.
|
||||
// ------------------------------------------------------------------------
|
||||
static constexpr const std::array<const char*, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_names = {
|
||||
{"X", "Y", "WheelX", "WheelY"}};
|
||||
static constexpr const std::array<const char*, 3> s_pointer_button_names = {{"LeftButton", "RightButton", "MiddleButton"}};
|
||||
|
||||
struct PointerAxisState
|
||||
{
|
||||
std::atomic<s32> delta;
|
||||
float last_value;
|
||||
};
|
||||
static std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES> s_host_pointer_positions;
|
||||
static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES>
|
||||
s_pointer_state;
|
||||
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Binding Parsing
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -166,8 +188,7 @@ std::vector<std::string_view> InputManager::SplitChord(const std::string_view& b
|
|||
return parts;
|
||||
}
|
||||
|
||||
bool InputManager::SplitBinding(
|
||||
const std::string_view& binding, std::string_view* source, std::string_view* sub_binding)
|
||||
bool InputManager::SplitBinding(const std::string_view& binding, std::string_view* source, std::string_view* sub_binding)
|
||||
{
|
||||
const std::string_view::size_type slash_pos = binding.find('/');
|
||||
if (slash_pos == std::string_view::npos)
|
||||
|
@ -192,9 +213,9 @@ std::optional<InputBindingKey> InputManager::ParseInputBindingKey(const std::str
|
|||
{
|
||||
return ParseHostKeyboardKey(source, sub_binding);
|
||||
}
|
||||
else if (StringUtil::StartsWith(source, "Mouse"))
|
||||
else if (StringUtil::StartsWith(source, "Pointer"))
|
||||
{
|
||||
return ParseHostMouseKey(source, sub_binding);
|
||||
return ParsePointerKey(source, sub_binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -241,17 +262,21 @@ std::string InputManager::ConvertInputBindingKeyToString(InputBindingKey key)
|
|||
{
|
||||
const std::optional<std::string> str(ConvertHostKeyboardCodeToString(key.data));
|
||||
if (str.has_value() && !str->empty())
|
||||
return StringUtil::StdStringFromFormat("Keyboard/%s", str->c_str());
|
||||
return fmt::format("Keyboard/{}", str->c_str());
|
||||
}
|
||||
else if (key.source_type == InputSourceType::Mouse)
|
||||
else if (key.source_type == InputSourceType::Pointer)
|
||||
{
|
||||
if (key.source_subtype == InputSubclass::MouseButton)
|
||||
return StringUtil::StdStringFromFormat("Mouse%u/Button%u", key.source_index, key.data);
|
||||
else if (key.source_subtype == InputSubclass::MousePointer)
|
||||
return StringUtil::StdStringFromFormat("Mouse%u/Pointer%u", key.source_index, key.data);
|
||||
else if (key.source_subtype == InputSubclass::MouseWheel)
|
||||
return StringUtil::StdStringFromFormat(
|
||||
"Mouse%u/Wheel%u%c", key.source_index, key.data, key.negative ? '-' : '+');
|
||||
if (key.source_subtype == InputSubclass::PointerButton)
|
||||
{
|
||||
if (key.data < s_pointer_button_names.size())
|
||||
return fmt::format("Pointer-{}/{}", u32{key.source_index}, s_pointer_button_names[key.data]);
|
||||
else
|
||||
return fmt::format("Pointer-{}/Button{}", u32{key.source_index}, key.data);
|
||||
}
|
||||
else if (key.source_subtype == InputSubclass::PointerAxis)
|
||||
{
|
||||
return fmt::format("Pointer-{}/{}{:c}", u32{key.source_index}, s_pointer_axis_names[key.data], key.negative ? '-' : '+');
|
||||
}
|
||||
}
|
||||
else if (key.source_type < InputSourceType::Count && s_input_sources[static_cast<u32>(key.source_type)])
|
||||
{
|
||||
|
@ -335,21 +360,23 @@ InputBindingKey InputManager::MakeHostKeyboardKey(s32 key_code)
|
|||
return key;
|
||||
}
|
||||
|
||||
InputBindingKey InputManager::MakeHostMouseButtonKey(s32 button_index)
|
||||
InputBindingKey InputManager::MakePointerButtonKey(u32 index, u32 button_index)
|
||||
{
|
||||
InputBindingKey key = {};
|
||||
key.source_type = InputSourceType::Mouse;
|
||||
key.source_subtype = InputSubclass::MouseButton;
|
||||
key.data = static_cast<u32>(button_index);
|
||||
key.source_index = index;
|
||||
key.source_type = InputSourceType::Pointer;
|
||||
key.source_subtype = InputSubclass::PointerButton;
|
||||
key.data = button_index;
|
||||
return key;
|
||||
}
|
||||
|
||||
InputBindingKey InputManager::MakeHostMouseWheelKey(s32 axis_index)
|
||||
InputBindingKey InputManager::MakePointerAxisKey(u32 index, InputPointerAxis axis)
|
||||
{
|
||||
InputBindingKey key = {};
|
||||
key.source_type = InputSourceType::Mouse;
|
||||
key.source_subtype = InputSubclass::MouseWheel;
|
||||
key.data = static_cast<u32>(axis_index);
|
||||
key.data = static_cast<u32>(axis);
|
||||
key.source_index = index;
|
||||
key.source_type = InputSourceType::Pointer;
|
||||
key.source_subtype = InputSubclass::PointerAxis;
|
||||
return key;
|
||||
}
|
||||
|
||||
|
@ -389,8 +416,7 @@ std::optional<InputSourceType> InputManager::ParseInputSourceString(const std::s
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> InputManager::ParseHostKeyboardKey(
|
||||
const std::string_view& source, const std::string_view& sub_binding)
|
||||
std::optional<InputBindingKey> InputManager::ParseHostKeyboardKey(const std::string_view& source, const std::string_view& sub_binding)
|
||||
{
|
||||
if (source != "Keyboard")
|
||||
return std::nullopt;
|
||||
|
@ -405,14 +431,15 @@ std::optional<InputBindingKey> InputManager::ParseHostKeyboardKey(
|
|||
return key;
|
||||
}
|
||||
|
||||
std::optional<InputBindingKey> InputManager::ParseHostMouseKey(
|
||||
const std::string_view& source, const std::string_view& sub_binding)
|
||||
std::optional<InputBindingKey> InputManager::ParsePointerKey(const std::string_view& source, const std::string_view& sub_binding)
|
||||
{
|
||||
if (source != "Mouse")
|
||||
const std::optional<s32> pointer_index = StringUtil::FromChars<s32>(source.substr(8));
|
||||
if (!pointer_index.has_value() || pointer_index.value() < 0)
|
||||
return std::nullopt;
|
||||
|
||||
InputBindingKey key = {};
|
||||
key.source_type = InputSourceType::Mouse;
|
||||
key.source_type = InputSourceType::Pointer;
|
||||
key.source_index = static_cast<u32>(pointer_index.value());
|
||||
|
||||
if (StringUtil::StartsWith(sub_binding, "Button"))
|
||||
{
|
||||
|
@ -420,16 +447,42 @@ std::optional<InputBindingKey> InputManager::ParseHostMouseKey(
|
|||
if (!button_number.has_value() || button_number.value() < 0)
|
||||
return std::nullopt;
|
||||
|
||||
key.source_subtype = InputSubclass::MouseButton;
|
||||
key.source_subtype = InputSubclass::PointerButton;
|
||||
key.data = static_cast<u32>(button_number.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
return key;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < s_pointer_axis_names.size(); i++)
|
||||
{
|
||||
if (StringUtil::StartsWith(sub_binding, s_pointer_axis_names[i]))
|
||||
{
|
||||
key.source_subtype = InputSubclass::PointerAxis;
|
||||
key.data = i;
|
||||
|
||||
const std::string_view dir_part(sub_binding.substr(std::strlen(s_pointer_axis_names[i])));
|
||||
if (dir_part == "+")
|
||||
key.negative = false;
|
||||
else if (dir_part == "-")
|
||||
key.negative = true;
|
||||
else
|
||||
return std::nullopt;
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < s_pointer_button_names.size(); i++)
|
||||
{
|
||||
if (sub_binding == s_pointer_button_names[i])
|
||||
{
|
||||
key.source_subtype = InputSubclass::PointerButton;
|
||||
key.data = i;
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Binding Enumeration
|
||||
|
@ -484,17 +537,16 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
|
|||
if (!bindings.empty())
|
||||
{
|
||||
// we use axes for all pad bindings to simplify things, and because they are pressure sensitive
|
||||
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index, bind_names](float value) {
|
||||
PAD::SetControllerState(pad_index, bind_index, value);
|
||||
}});
|
||||
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index, bind_names](
|
||||
float value) { PAD::SetControllerState(pad_index, bind_index, value); }});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 macro_button_index = 0; macro_button_index < PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++)
|
||||
{
|
||||
const std::vector<std::string> bindings(si.GetStringList(section.c_str(),
|
||||
StringUtil::StdStringFromFormat("Macro%u", macro_button_index + 1).c_str()));
|
||||
const std::vector<std::string> bindings(
|
||||
si.GetStringList(section.c_str(), StringUtil::StdStringFromFormat("Macro%u", macro_button_index + 1).c_str()));
|
||||
if (!bindings.empty())
|
||||
{
|
||||
AddBindings(bindings, InputButtonEventHandler{[pad_index, macro_button_index](bool state) {
|
||||
|
@ -547,14 +599,30 @@ bool InputManager::HasAnyBindingsForKey(InputBindingKey key)
|
|||
return (s_binding_map.find(key.MaskDirection()) != s_binding_map.end());
|
||||
}
|
||||
|
||||
bool InputManager::HasAnyBindingsForSource(InputBindingKey key)
|
||||
{
|
||||
std::unique_lock lock(s_binding_map_write_lock);
|
||||
for (const auto& it : s_binding_map)
|
||||
{
|
||||
const InputBindingKey& okey = it.first;
|
||||
if (okey.source_type == key.source_type && okey.source_index == key.source_index &&
|
||||
okey.source_subtype == key.source_subtype)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputManager::IsAxisHandler(const InputEventHandler& handler)
|
||||
{
|
||||
return std::holds_alternative<InputAxisEventHandler>(handler);
|
||||
}
|
||||
|
||||
bool InputManager::InvokeEvents(InputBindingKey key, float value)
|
||||
bool InputManager::InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key)
|
||||
{
|
||||
if (DoEventHook(key, value))
|
||||
if (PreprocessEvent(key, value, generic_key))
|
||||
return true;
|
||||
|
||||
// find all the bindings associated with this key
|
||||
|
@ -628,8 +696,7 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value)
|
|||
binding->current_mask = new_mask;
|
||||
|
||||
// invert if we're negative, since the handler expects 0..1
|
||||
const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value :
|
||||
0.0f);
|
||||
const float value_to_pass = (negative ? ((value < 0.0f) ? -value : 0.0f) : (value > 0.0f) ? value : 0.0f);
|
||||
|
||||
// axes are fired regardless of a state change, unless they're zero
|
||||
// (but going from not-zero to zero will still fire, because of the full state)
|
||||
|
@ -654,6 +721,89 @@ bool InputManager::InvokeEvents(InputBindingKey key, float value)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool InputManager::PreprocessEvent(InputBindingKey key, float value, GenericInputBinding generic_key)
|
||||
{
|
||||
if (DoEventHook(key, value))
|
||||
return true;
|
||||
|
||||
// does imgui want the event?
|
||||
if (key.source_type == InputSourceType::Keyboard)
|
||||
{
|
||||
if (ImGuiManager::ProcessHostKeyEvent(key, value))
|
||||
return true;
|
||||
}
|
||||
else if (key.source_type == InputSourceType::Pointer && key.source_subtype == InputSubclass::PointerButton)
|
||||
{
|
||||
if (ImGuiManager::ProcessPointerButtonEvent(key, value))
|
||||
return true;
|
||||
}
|
||||
else if (generic_key != GenericInputBinding::Unknown)
|
||||
{
|
||||
if (ImGuiManager::ProcessGenericInputEvent(generic_key, value))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputManager::GenerateRelativeMouseEvents()
|
||||
{
|
||||
for (u32 device = 0; device < MAX_POINTER_DEVICES; device++)
|
||||
{
|
||||
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
|
||||
{
|
||||
PointerAxisState& state = s_pointer_state[device][axis];
|
||||
const float delta = static_cast<float>(state.delta.exchange(0, std::memory_order_acquire)) / 65536.0f;
|
||||
const float unclamped_value = delta * s_pointer_axis_scale[axis];
|
||||
|
||||
const InputBindingKey key(MakePointerAxisKey(device, static_cast<InputPointerAxis>(axis)));
|
||||
if (axis >= static_cast<u32>(InputPointerAxis::WheelX) && ImGuiManager::ProcessPointerAxisEvent(key, unclamped_value))
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y)
|
||||
{
|
||||
const float dx = x - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::X)], x);
|
||||
const float dy = y - std::exchange(s_host_pointer_positions[index][static_cast<u8>(InputPointerAxis::Y)], y);
|
||||
|
||||
if (dx != 0.0f)
|
||||
UpdatePointerRelativeDelta(index, InputPointerAxis::X, dx);
|
||||
if (dy != 0.0f)
|
||||
UpdatePointerRelativeDelta(index, InputPointerAxis::Y, dy);
|
||||
|
||||
ImGuiManager::UpdateMousePosition(x, y);
|
||||
}
|
||||
|
||||
void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input)
|
||||
{
|
||||
s_pointer_state[index][static_cast<u8>(axis)].delta.fetch_add(static_cast<s32>(d * 65536.0f), std::memory_order_release);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Vibration
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -684,7 +834,8 @@ void InputManager::SetPadVibrationIntensity(u32 pad_index, float large_or_single
|
|||
{
|
||||
// both motors are bound to the same source, do an optimal update
|
||||
large_motor.last_update_time = Common::Timer::GetCurrentValue();
|
||||
large_motor.source->UpdateMotorState(large_motor.binding, small_motor.binding, large_or_single_motor_intensity, small_motor_intensity);
|
||||
large_motor.source->UpdateMotorState(
|
||||
large_motor.binding, small_motor.binding, large_or_single_motor_intensity, small_motor_intensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -801,7 +952,11 @@ bool InputManager::DoEventHook(InputBindingKey key, float value)
|
|||
return false;
|
||||
|
||||
const InputInterceptHook::CallbackResult action = m_event_intercept_callback(key, value);
|
||||
return (action == InputInterceptHook::CallbackResult::StopProcessingEvent);
|
||||
if (action >= InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent)
|
||||
m_event_intercept_callback = {};
|
||||
|
||||
return (action == InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent ||
|
||||
action == InputInterceptHook::CallbackResult::StopProcessingEvent);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -825,6 +980,17 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
|
|||
// falling back to the base configuration.
|
||||
for (u32 pad = 0; pad < PAD::NUM_CONTROLLER_PORTS; pad++)
|
||||
AddPadBindings(binding_si, pad, PAD::GetDefaultPadType(pad));
|
||||
|
||||
for (u32 axis = 0; axis < static_cast<u32>(InputPointerAxis::Count); axis++)
|
||||
{
|
||||
// From lilypad: 1 mouse pixel = 1/8th way down.
|
||||
const float default_scale = (axis <= static_cast<u32>(InputPointerAxis::Y)) ? 8.0f : 1.0f;
|
||||
const float invert =
|
||||
si.GetBoolValue("Pad", fmt::format("Pointer{}Invert", s_pointer_axis_names[axis]).c_str(), false) ? -1.0f : 1.0f;
|
||||
s_pointer_axis_scale[axis] =
|
||||
invert /
|
||||
std::max(si.GetFloatValue("Pad", fmt::format("Pointer{}Scale", s_pointer_axis_names[axis]).c_str(), default_scale), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -851,6 +1017,8 @@ void InputManager::PollSources()
|
|||
s_input_sources[i]->PollEvents();
|
||||
}
|
||||
|
||||
GenerateRelativeMouseEvents();
|
||||
|
||||
if (VMManager::GetState() == VMState::Running && !s_pad_vibration_array.empty())
|
||||
UpdateContinuedVibration();
|
||||
}
|
||||
|
@ -953,7 +1121,8 @@ GenericInputBindingMapping InputManager::GetGenericBindingMapping(const std::str
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
static void UpdateInputSourceState(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock, InputSourceType type, bool default_state)
|
||||
static void UpdateInputSourceState(
|
||||
SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock, InputSourceType type, bool default_state)
|
||||
{
|
||||
const bool enabled = si.GetBoolValue("InputSources", InputManager::InputSourceToString(type), default_state);
|
||||
if (enabled)
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
enum class InputSourceType : u32
|
||||
{
|
||||
Keyboard,
|
||||
Mouse,
|
||||
Pointer,
|
||||
#ifdef _WIN32
|
||||
//DInput,
|
||||
XInput,
|
||||
|
@ -44,9 +44,8 @@ enum class InputSubclass : u32
|
|||
{
|
||||
None = 0,
|
||||
|
||||
MouseButton = 0,
|
||||
MousePointer = 1,
|
||||
MouseWheel = 2,
|
||||
PointerButton = 0,
|
||||
PointerAxis = 1,
|
||||
|
||||
ControllerButton = 0,
|
||||
ControllerAxis = 1,
|
||||
|
@ -102,7 +101,9 @@ struct InputInterceptHook
|
|||
enum class CallbackResult
|
||||
{
|
||||
StopProcessingEvent,
|
||||
ContinueProcessingEvent
|
||||
ContinueProcessingEvent,
|
||||
RemoveHookAndStopProcessingEvent,
|
||||
RemoveHookAndContinueProcessingEvent,
|
||||
};
|
||||
|
||||
using Callback = std::function<CallbackResult(InputBindingKey key, float value)>;
|
||||
|
@ -119,14 +120,19 @@ struct HotkeyInfo
|
|||
#define DECLARE_HOTKEY_LIST(name) extern const HotkeyInfo name[]
|
||||
#define BEGIN_HOTKEY_LIST(name) const HotkeyInfo name[] = {
|
||||
#define DEFINE_HOTKEY(name, category, display_name, handler) {(name), (category), (display_name), (handler)},
|
||||
#define END_HOTKEY_LIST() {nullptr, nullptr, nullptr, nullptr} };
|
||||
#define END_HOTKEY_LIST() \
|
||||
{ \
|
||||
nullptr, nullptr, nullptr, nullptr \
|
||||
} \
|
||||
} \
|
||||
;
|
||||
|
||||
DECLARE_HOTKEY_LIST(g_vm_manager_hotkeys);
|
||||
DECLARE_HOTKEY_LIST(g_gs_hotkeys);
|
||||
DECLARE_HOTKEY_LIST(g_host_hotkeys);
|
||||
|
||||
/// Generic input bindings. These roughly match a DualShock 4 or XBox One controller.
|
||||
/// They are used for automatic binding to PS2 controller types.
|
||||
/// They are used for automatic binding to PS2 controller types, and for big picture mode navigation.
|
||||
enum class GenericInputBinding : u8
|
||||
{
|
||||
Unknown,
|
||||
|
@ -169,6 +175,16 @@ enum class GenericInputBinding : u8
|
|||
};
|
||||
using GenericInputBindingMapping = std::vector<std::pair<GenericInputBinding, std::string>>;
|
||||
|
||||
/// Host mouse relative axes are X, Y, wheel horizontal, wheel vertical.
|
||||
enum class InputPointerAxis : u8
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
WheelX,
|
||||
WheelY,
|
||||
Count
|
||||
};
|
||||
|
||||
/// External input source class.
|
||||
class InputSource;
|
||||
|
||||
|
@ -177,6 +193,9 @@ namespace InputManager
|
|||
/// Minimum interval between vibration updates when the effect is continuous.
|
||||
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
|
||||
|
||||
/// Maximum number of host mouse devices.
|
||||
static constexpr u32 MAX_POINTER_DEVICES = 1;
|
||||
|
||||
/// Returns a pointer to the external input source class, if present.
|
||||
InputSource* GetInputSourceInterface(InputSourceType type);
|
||||
|
||||
|
@ -196,10 +215,11 @@ namespace InputManager
|
|||
InputBindingKey MakeHostKeyboardKey(s32 key_code);
|
||||
|
||||
/// Creates a key for a host-specific button.
|
||||
InputBindingKey MakeHostMouseButtonKey(s32 button_index);
|
||||
InputBindingKey MakePointerButtonKey(u32 index, u32 button_index);
|
||||
|
||||
/// Creates a key for a host-specific mouse wheel axis (0 = vertical, 1 = horizontal).
|
||||
InputBindingKey MakeHostMouseWheelKey(s32 axis_index);
|
||||
/// Creates a key for a host-specific mouse relative event
|
||||
/// (axis 0 = horizontal, 1 = vertical, 2 = wheel horizontal, 3 = wheel vertical).
|
||||
InputBindingKey MakePointerAxisKey(u32 index, InputPointerAxis axis);
|
||||
|
||||
/// Parses an input binding key string.
|
||||
std::optional<InputBindingKey> ParseInputBindingKey(const std::string_view& binding);
|
||||
|
@ -235,12 +255,16 @@ namespace InputManager
|
|||
void PollSources();
|
||||
|
||||
/// Returns true if any bindings exist for the specified key.
|
||||
/// This is the only function which can be safely called on another thread.
|
||||
/// Can be safely called on another thread.
|
||||
bool HasAnyBindingsForKey(InputBindingKey key);
|
||||
|
||||
/// Returns true if any bindings exist for the specified source + index.
|
||||
/// Can be safely called on another thread.
|
||||
bool HasAnyBindingsForSource(InputBindingKey key);
|
||||
|
||||
/// Updates internal state for any binds for this key, and fires callbacks as needed.
|
||||
/// Returns true if anything was bound to this key, otherwise false.
|
||||
bool InvokeEvents(InputBindingKey key, float value);
|
||||
bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown);
|
||||
|
||||
/// Sets a hook which can be used to intercept events before they're processed by the normal bindings.
|
||||
/// This is typically used when binding new controls to detect what gets pressed.
|
||||
|
@ -259,6 +283,15 @@ namespace InputManager
|
|||
/// Zeros all vibration intensities. Call when pausing.
|
||||
/// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes.
|
||||
void PauseVibration();
|
||||
|
||||
/// Updates absolute pointer position. Can call from UI thread, use when the host only reports absolute coordinates.
|
||||
void UpdatePointerAbsolutePosition(u32 index, float x, float y);
|
||||
|
||||
/// Updates relative pointer position. Can call from the UI thread, use when host supports relative coordinate reporting.
|
||||
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
|
||||
|
||||
/// Returns true if any bindings are present which require relative mouse movement.
|
||||
bool HasPointerAxisBinds();
|
||||
} // namespace InputManager
|
||||
|
||||
namespace Host
|
||||
|
@ -268,4 +301,4 @@ namespace Host
|
|||
|
||||
/// Called when an input device is disconnected.
|
||||
void OnInputDeviceDisconnected(const std::string_view& identifier);
|
||||
}
|
||||
} // namespace Host
|
||||
|
|
|
@ -498,7 +498,10 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent
|
|||
return false;
|
||||
|
||||
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button));
|
||||
return InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f);
|
||||
const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ?
|
||||
s_sdl_generic_binding_button_mapping[ev->button] :
|
||||
GenericInputBinding::Unknown;
|
||||
return InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key);
|
||||
}
|
||||
|
||||
std::vector<InputBindingKey> SDLInputSource::EnumerateMotors()
|
||||
|
|
|
@ -29,7 +29,7 @@ const char* XInputSource::s_axis_names[XInputSource::NUM_AXES] = {
|
|||
"LeftTrigger", // AXIS_TRIGGERLEFT
|
||||
"RightTrigger", // AXIS_TRIGGERRIGHT
|
||||
};
|
||||
static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = {
|
||||
static const GenericInputBinding s_xinput_generic_binding_axis_mapping[][2] = {
|
||||
{GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // AXIS_LEFTX
|
||||
{GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // AXIS_LEFTY
|
||||
{GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // AXIS_RIGHTX
|
||||
|
@ -72,7 +72,7 @@ const u16 XInputSource::s_button_masks[XInputSource::NUM_BUTTONS] = {
|
|||
XINPUT_GAMEPAD_Y,
|
||||
0x400, // XINPUT_GAMEPAD_GUIDE
|
||||
};
|
||||
static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = {
|
||||
static const GenericInputBinding s_xinput_generic_binding_button_mapping[] = {
|
||||
GenericInputBinding::DPadUp, // XINPUT_GAMEPAD_DPAD_UP
|
||||
GenericInputBinding::DPadDown, // XINPUT_GAMEPAD_DPAD_DOWN
|
||||
GenericInputBinding::DPadLeft, // XINPUT_GAMEPAD_DPAD_LEFT
|
||||
|
@ -347,19 +347,19 @@ bool XInputSource::GetGenericBindingMapping(const std::string_view& device, Gene
|
|||
|
||||
// assume all buttons are present.
|
||||
const s32 pid = player_id.value();
|
||||
for (u32 i = 0; i < std::size(s_sdl_generic_binding_axis_mapping); i++)
|
||||
for (u32 i = 0; i < std::size(s_xinput_generic_binding_axis_mapping); i++)
|
||||
{
|
||||
const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0];
|
||||
const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1];
|
||||
const GenericInputBinding negative = s_xinput_generic_binding_axis_mapping[i][0];
|
||||
const GenericInputBinding positive = s_xinput_generic_binding_axis_mapping[i][1];
|
||||
if (negative != GenericInputBinding::Unknown)
|
||||
mapping->emplace_back(negative, StringUtil::StdStringFromFormat("XInput-%d/-%s", pid, s_axis_names[i]));
|
||||
|
||||
if (positive != GenericInputBinding::Unknown)
|
||||
mapping->emplace_back(positive, StringUtil::StdStringFromFormat("XInput-%d/+%s", pid, s_axis_names[i]));
|
||||
}
|
||||
for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++)
|
||||
for (u32 i = 0; i < std::size(s_xinput_generic_binding_button_mapping); i++)
|
||||
{
|
||||
const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i];
|
||||
const GenericInputBinding binding = s_xinput_generic_binding_button_mapping[i];
|
||||
if (binding != GenericInputBinding::Unknown)
|
||||
mapping->emplace_back(binding, StringUtil::StdStringFromFormat("XInput-%d/%s", pid, s_button_names[i]));
|
||||
}
|
||||
|
@ -435,9 +435,12 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
|
|||
const u16 button_mask = s_button_masks[button];
|
||||
if ((old_button_bits & button_mask) != (new_button_bits & button_mask))
|
||||
{
|
||||
const GenericInputBinding generic_key = (button < std::size(s_xinput_generic_binding_button_mapping)) ?
|
||||
s_xinput_generic_binding_button_mapping[button] : GenericInputBinding::Unknown;
|
||||
const float value = ((new_button_bits & button_mask) != 0) ? 1.0f : 0.0f;
|
||||
InputManager::InvokeEvents(
|
||||
MakeGenericControllerButtonKey(InputSourceType::XInput, index, button),
|
||||
(new_button_bits & button_mask) != 0);
|
||||
value, generic_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,8 +237,13 @@ void PAD::SetDefaultConfig(SettingsInterface& si)
|
|||
si.SetBoolValue("InputSources", "SDL", true);
|
||||
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
|
||||
si.SetBoolValue("InputSources", "XInput", false);
|
||||
si.SetBoolValue("InputSources", "RawInput", false);
|
||||
si.SetBoolValue("Pad", "MultitapPort1", false);
|
||||
si.SetBoolValue("Pad", "MultitapPort2", false);
|
||||
si.SetFloatValue("Pad", "PointerXScale", 8.0f);
|
||||
si.SetFloatValue("Pad", "PointerYScale", 8.0f);
|
||||
si.SetBoolValue("Pad", "PointerXInvert", false);
|
||||
si.SetBoolValue("Pad", "PointerYInvert", false);
|
||||
|
||||
// PCSX2 Controller Settings - Controller 1 / Controller 2 / ...
|
||||
// Use the automapper to set this up.
|
||||
|
|
Loading…
Reference in New Issue