Merge pull request #7452 from german77/controller_navigation
yuzu: Implement basic controller UI navigation
This commit is contained in:
commit
55d6b095e5
|
@ -152,6 +152,8 @@ add_executable(yuzu
|
||||||
main.ui
|
main.ui
|
||||||
uisettings.cpp
|
uisettings.cpp
|
||||||
uisettings.h
|
uisettings.h
|
||||||
|
util/controller_navigation.cpp
|
||||||
|
util/controller_navigation.h
|
||||||
util/limitable_input_dialog.cpp
|
util/limitable_input_dialog.cpp
|
||||||
util/limitable_input_dialog.h
|
util/limitable_input_dialog.h
|
||||||
util/overlay_dialog.cpp
|
util/overlay_dialog.cpp
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/frontend/applets/controller.h"
|
#include "core/frontend/applets/controller.h"
|
||||||
|
|
||||||
class GMainWindow;
|
class GMainWindow;
|
||||||
|
@ -32,8 +31,9 @@ class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
|
class HIDCore;
|
||||||
enum class NpadStyleIndex : u8;
|
enum class NpadStyleIndex : u8;
|
||||||
}
|
} // namespace Core::HID
|
||||||
|
|
||||||
class QtControllerSelectorDialog final : public QDialog {
|
class QtControllerSelectorDialog final : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <QApplication>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
#include "core/hle/lock.h"
|
#include "core/hle/lock.h"
|
||||||
#include "yuzu/applets/qt_profile_select.h"
|
#include "yuzu/applets/qt_profile_select.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
|
#include "yuzu/util/controller_navigation.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
|
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
|
||||||
|
@ -45,7 +47,7 @@ QPixmap GetIcon(Common::UUID uuid) {
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent)
|
||||||
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
|
||||||
outer_layout = new QVBoxLayout;
|
outer_layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
@ -65,6 +67,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
||||||
tree_view = new QTreeView;
|
tree_view = new QTreeView;
|
||||||
item_model = new QStandardItemModel(tree_view);
|
item_model = new QStandardItemModel(tree_view);
|
||||||
tree_view->setModel(item_model);
|
tree_view->setModel(item_model);
|
||||||
|
controller_navigation = new ControllerNavigation(hid_core, this);
|
||||||
|
|
||||||
tree_view->setAlternatingRowColors(true);
|
tree_view->setAlternatingRowColors(true);
|
||||||
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
||||||
|
@ -91,6 +94,14 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
||||||
scroll_area->setLayout(layout);
|
scroll_area->setLayout(layout);
|
||||||
|
|
||||||
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
|
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
|
||||||
|
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||||
|
[this](Qt::Key key) {
|
||||||
|
if (!this->isActiveWindow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(tree_view, event);
|
||||||
|
});
|
||||||
|
|
||||||
const auto& profiles = profile_manager->GetAllUsers();
|
const auto& profiles = profile_manager->GetAllUsers();
|
||||||
for (const auto& user : profiles) {
|
for (const auto& user : profiles) {
|
||||||
|
@ -113,7 +124,9 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
|
||||||
resize(550, 400);
|
resize(550, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
|
QtProfileSelectionDialog::~QtProfileSelectionDialog() {
|
||||||
|
controller_navigation->UnloadController();
|
||||||
|
};
|
||||||
|
|
||||||
int QtProfileSelectionDialog::exec() {
|
int QtProfileSelectionDialog::exec() {
|
||||||
// Skip profile selection when there's only one.
|
// Skip profile selection when there's only one.
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/frontend/applets/profile_select.h"
|
#include "core/frontend/applets/profile_select.h"
|
||||||
#include "core/hle/service/acc/profile_manager.h"
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
|
|
||||||
|
class ControllerNavigation;
|
||||||
class GMainWindow;
|
class GMainWindow;
|
||||||
class QDialogButtonBox;
|
class QDialogButtonBox;
|
||||||
class QGraphicsScene;
|
class QGraphicsScene;
|
||||||
|
@ -20,11 +21,15 @@ class QStandardItem;
|
||||||
class QStandardItemModel;
|
class QStandardItemModel;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
class HIDCore;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
class QtProfileSelectionDialog final : public QDialog {
|
class QtProfileSelectionDialog final : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit QtProfileSelectionDialog(QWidget* parent);
|
explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);
|
||||||
~QtProfileSelectionDialog() override;
|
~QtProfileSelectionDialog() override;
|
||||||
|
|
||||||
int exec() override;
|
int exec() override;
|
||||||
|
@ -51,6 +56,7 @@ private:
|
||||||
QDialogButtonBox* buttons;
|
QDialogButtonBox* buttons;
|
||||||
|
|
||||||
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
|
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
|
||||||
|
ControllerNavigation* controller_navigation = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
|
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/file_sys/patch_manager.h"
|
#include "core/file_sys/patch_manager.h"
|
||||||
#include "core/file_sys/registered_cache.h"
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
#include "yuzu/game_list_worker.h"
|
#include "yuzu/game_list_worker.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
|
#include "yuzu/util/controller_navigation.h"
|
||||||
|
|
||||||
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
|
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
|
||||||
: QObject(parent), gamelist{gamelist} {}
|
: QObject(parent), gamelist{gamelist} {}
|
||||||
|
@ -312,6 +314,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||||
this->main_window = parent;
|
this->main_window = parent;
|
||||||
layout = new QVBoxLayout;
|
layout = new QVBoxLayout;
|
||||||
tree_view = new QTreeView;
|
tree_view = new QTreeView;
|
||||||
|
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
|
||||||
search_field = new GameListSearchField(this);
|
search_field = new GameListSearchField(this);
|
||||||
item_model = new QStandardItemModel(tree_view);
|
item_model = new QStandardItemModel(tree_view);
|
||||||
tree_view->setModel(item_model);
|
tree_view->setModel(item_model);
|
||||||
|
@ -341,6 +344,18 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||||
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
||||||
connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
|
connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
|
||||||
|
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||||
|
[this](Qt::Key key) {
|
||||||
|
// Avoid pressing buttons while playing
|
||||||
|
if (system.IsPoweredOn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->isActiveWindow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(tree_view, event);
|
||||||
|
});
|
||||||
|
|
||||||
// We must register all custom types with the Qt Automoc system so that we are able to use
|
// We must register all custom types with the Qt Automoc system so that we are able to use
|
||||||
// it with signals/slots. In this case, QList falls under the umbrells of custom types.
|
// it with signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||||
|
@ -353,7 +368,12 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameList::UnloadController() {
|
||||||
|
controller_navigation->UnloadController();
|
||||||
|
}
|
||||||
|
|
||||||
GameList::~GameList() {
|
GameList::~GameList() {
|
||||||
|
UnloadController();
|
||||||
emit ShouldCancelWorker();
|
emit ShouldCancelWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "uisettings.h"
|
#include "uisettings.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
|
|
||||||
|
class ControllerNavigation;
|
||||||
class GameListWorker;
|
class GameListWorker;
|
||||||
class GameListSearchField;
|
class GameListSearchField;
|
||||||
class GameListDir;
|
class GameListDir;
|
||||||
|
@ -88,6 +89,9 @@ public:
|
||||||
void SaveInterfaceLayout();
|
void SaveInterfaceLayout();
|
||||||
void LoadInterfaceLayout();
|
void LoadInterfaceLayout();
|
||||||
|
|
||||||
|
/// Disables events from the emulated controller
|
||||||
|
void UnloadController();
|
||||||
|
|
||||||
static const QStringList supported_file_extensions;
|
static const QStringList supported_file_extensions;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -143,6 +147,7 @@ private:
|
||||||
QStandardItemModel* item_model = nullptr;
|
QStandardItemModel* item_model = nullptr;
|
||||||
GameListWorker* current_worker = nullptr;
|
GameListWorker* current_worker = nullptr;
|
||||||
QFileSystemWatcher* watcher = nullptr;
|
QFileSystemWatcher* watcher = nullptr;
|
||||||
|
ControllerNavigation* controller_navigation = nullptr;
|
||||||
CompatibilityList compatibility_list;
|
CompatibilityList compatibility_list;
|
||||||
|
|
||||||
friend class GameListSearchField;
|
friend class GameListSearchField;
|
||||||
|
|
|
@ -449,7 +449,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::ProfileSelectorSelectProfile() {
|
void GMainWindow::ProfileSelectorSelectProfile() {
|
||||||
QtProfileSelectionDialog dialog(this);
|
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
|
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
|
||||||
Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
|
Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
|
||||||
Qt::WindowCloseButtonHint);
|
Qt::WindowCloseButtonHint);
|
||||||
|
@ -1346,7 +1346,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::SelectAndSetCurrentUser() {
|
void GMainWindow::SelectAndSetCurrentUser() {
|
||||||
QtProfileSelectionDialog dialog(this);
|
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||||
dialog.setWindowModality(Qt::WindowModal);
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
|
@ -1608,7 +1608,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||||
if (has_user_save) {
|
if (has_user_save) {
|
||||||
// User save data
|
// User save data
|
||||||
const auto select_profile = [this] {
|
const auto select_profile = [this] {
|
||||||
QtProfileSelectionDialog dialog(this);
|
QtProfileSelectionDialog dialog(system->HIDCore(), this);
|
||||||
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||||
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||||
dialog.setWindowModality(Qt::WindowModal);
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
|
@ -3376,7 +3376,10 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||||
UpdateUISettings();
|
UpdateUISettings();
|
||||||
game_list->SaveInterfaceLayout();
|
game_list->SaveInterfaceLayout();
|
||||||
hotkey_registry.SaveHotkeys();
|
hotkey_registry.SaveHotkeys();
|
||||||
|
|
||||||
|
// Unload controllers early
|
||||||
controller_dialog->UnloadController();
|
controller_dialog->UnloadController();
|
||||||
|
game_list->UnloadController();
|
||||||
system->HIDCore().UnloadInputDevices();
|
system->HIDCore().UnloadInputDevices();
|
||||||
|
|
||||||
// Shutdown session if the emu thread is active...
|
// Shutdown session if the emu thread is active...
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#include "common/settings_input.h"
|
||||||
|
#include "core/hid/emulated_controller.h"
|
||||||
|
#include "core/hid/hid_core.h"
|
||||||
|
#include "yuzu/util/controller_navigation.h"
|
||||||
|
|
||||||
|
ControllerNavigation::ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent) {
|
||||||
|
player1_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||||
|
handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||||
|
Core::HID::ControllerUpdateCallback engine_callback{
|
||||||
|
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
|
||||||
|
.is_npad_service = false,
|
||||||
|
};
|
||||||
|
player1_callback_key = player1_controller->SetCallback(engine_callback);
|
||||||
|
handheld_callback_key = handheld_controller->SetCallback(engine_callback);
|
||||||
|
is_controller_set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerNavigation::~ControllerNavigation() {
|
||||||
|
UnloadController();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerNavigation::UnloadController() {
|
||||||
|
if (is_controller_set) {
|
||||||
|
player1_controller->DeleteCallback(player1_callback_key);
|
||||||
|
handheld_controller->DeleteCallback(handheld_callback_key);
|
||||||
|
is_controller_set = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_button,
|
||||||
|
Qt::Key key) {
|
||||||
|
if (button_values[native_button].value && !button_values[native_button].locked) {
|
||||||
|
emit TriggerKeyboardEvent(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
if (type == Core::HID::ControllerTriggerType::Button) {
|
||||||
|
ControllerUpdateButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Core::HID::ControllerTriggerType::Stick) {
|
||||||
|
ControllerUpdateStick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerNavigation::ControllerUpdateButton() {
|
||||||
|
const auto controller_type = player1_controller->GetNpadStyleIndex();
|
||||||
|
const auto& player1_buttons = player1_controller->GetButtonsValues();
|
||||||
|
const auto& handheld_buttons = handheld_controller->GetButtonsValues();
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < player1_buttons.size(); ++i) {
|
||||||
|
const bool button = player1_buttons[i].value || handheld_buttons[i].value;
|
||||||
|
// Trigger only once
|
||||||
|
button_values[i].locked = button == button_values[i].value;
|
||||||
|
button_values[i].value = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (controller_type) {
|
||||||
|
case Core::HID::NpadStyleIndex::ProController:
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||||
|
case Core::HID::NpadStyleIndex::Handheld:
|
||||||
|
case Core::HID::NpadStyleIndex::GameCube:
|
||||||
|
TriggerButton(Settings::NativeButton::A, Qt::Key_Enter);
|
||||||
|
TriggerButton(Settings::NativeButton::B, Qt::Key_Escape);
|
||||||
|
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Down);
|
||||||
|
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Left);
|
||||||
|
TriggerButton(Settings::NativeButton::DRight, Qt::Key_Right);
|
||||||
|
TriggerButton(Settings::NativeButton::DUp, Qt::Key_Up);
|
||||||
|
break;
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||||
|
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Enter);
|
||||||
|
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Escape);
|
||||||
|
break;
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||||
|
TriggerButton(Settings::NativeButton::X, Qt::Key_Enter);
|
||||||
|
TriggerButton(Settings::NativeButton::A, Qt::Key_Escape);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerNavigation::ControllerUpdateStick() {
|
||||||
|
const auto controller_type = player1_controller->GetNpadStyleIndex();
|
||||||
|
const auto& player1_sticks = player1_controller->GetSticksValues();
|
||||||
|
const auto& handheld_sticks = player1_controller->GetSticksValues();
|
||||||
|
bool update = false;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < player1_sticks.size(); ++i) {
|
||||||
|
const Common::Input::StickStatus stick{
|
||||||
|
.left = player1_sticks[i].left || handheld_sticks[i].left,
|
||||||
|
.right = player1_sticks[i].right || handheld_sticks[i].right,
|
||||||
|
.up = player1_sticks[i].up || handheld_sticks[i].up,
|
||||||
|
.down = player1_sticks[i].down || handheld_sticks[i].down,
|
||||||
|
};
|
||||||
|
// Trigger only once
|
||||||
|
if (stick.down != stick_values[i].down || stick.left != stick_values[i].left ||
|
||||||
|
stick.right != stick_values[i].right || stick.up != stick_values[i].up) {
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
stick_values[i] = stick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!update) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (controller_type) {
|
||||||
|
case Core::HID::NpadStyleIndex::ProController:
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||||
|
case Core::HID::NpadStyleIndex::Handheld:
|
||||||
|
case Core::HID::NpadStyleIndex::GameCube:
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].down) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].left) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].right) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].up) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconLeft:
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].left) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].up) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].down) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::LStick].right) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||||
|
if (stick_values[Settings::NativeAnalog::RStick].right) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Down);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::RStick].down) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Left);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::RStick].up) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stick_values[Settings::NativeAnalog::RStick].left) {
|
||||||
|
emit TriggerKeyboardEvent(Qt::Key_Up);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2021 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "common/settings_input.h"
|
||||||
|
|
||||||
|
namespace Core::HID {
|
||||||
|
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
|
||||||
|
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
|
||||||
|
enum class ControllerTriggerType;
|
||||||
|
class EmulatedController;
|
||||||
|
class HIDCore;
|
||||||
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
class ControllerNavigation : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent = nullptr);
|
||||||
|
~ControllerNavigation();
|
||||||
|
|
||||||
|
/// Disables events from the emulated controller
|
||||||
|
void UnloadController();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void TriggerKeyboardEvent(Qt::Key key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void TriggerButton(Settings::NativeButton::Values native_button, Qt::Key key);
|
||||||
|
void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
|
||||||
|
|
||||||
|
void ControllerUpdateButton();
|
||||||
|
|
||||||
|
void ControllerUpdateStick();
|
||||||
|
|
||||||
|
Core::HID::ButtonValues button_values{};
|
||||||
|
Core::HID::SticksValues stick_values{};
|
||||||
|
|
||||||
|
int player1_callback_key{};
|
||||||
|
int handheld_callback_key{};
|
||||||
|
bool is_controller_set{};
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
Core::HID::EmulatedController* player1_controller;
|
||||||
|
Core::HID::EmulatedController* handheld_controller;
|
||||||
|
};
|
Loading…
Reference in New Issue