mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 07:05:40 +12:00
Add controller support to Qt (#475)
* Add controllers to Qt Co-Authored-By: Nadia Holmquist Pedersen <893884+nadiaholmquist@users.noreply.github.com> * Remove debug logs * Bonk --------- Co-authored-by: Nadia Holmquist Pedersen <893884+nadiaholmquist@users.noreply.github.com>
This commit is contained in:
parent
429dc2a944
commit
3b9490e633
2 changed files with 130 additions and 3 deletions
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
|
@ -13,8 +15,8 @@
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
#include "input_mappings.hpp"
|
#include "input_mappings.hpp"
|
||||||
#include "panda_qt/about_window.hpp"
|
#include "panda_qt/about_window.hpp"
|
||||||
#include "panda_qt/config_window.hpp"
|
|
||||||
#include "panda_qt/cheats_window.hpp"
|
#include "panda_qt/cheats_window.hpp"
|
||||||
|
#include "panda_qt/config_window.hpp"
|
||||||
#include "panda_qt/screen.hpp"
|
#include "panda_qt/screen.hpp"
|
||||||
#include "panda_qt/text_editor.hpp"
|
#include "panda_qt/text_editor.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
@ -96,6 +98,10 @@ class MainWindow : public QMainWindow {
|
||||||
TextEditorWindow* luaEditor;
|
TextEditorWindow* luaEditor;
|
||||||
QMenuBar* menuBar = nullptr;
|
QMenuBar* menuBar = nullptr;
|
||||||
|
|
||||||
|
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||||
|
SDL_GameController* gameController = nullptr;
|
||||||
|
int gameControllerID = 0;
|
||||||
|
|
||||||
void swapEmuBuffer();
|
void swapEmuBuffer();
|
||||||
void emuThreadMainLoop();
|
void emuThreadMainLoop();
|
||||||
void selectLuaFile();
|
void selectLuaFile();
|
||||||
|
@ -104,6 +110,8 @@ class MainWindow : public QMainWindow {
|
||||||
void openLuaEditor();
|
void openLuaEditor();
|
||||||
void openCheatsEditor();
|
void openCheatsEditor();
|
||||||
void showAboutMenu();
|
void showAboutMenu();
|
||||||
|
void initControllers();
|
||||||
|
void pollControllers();
|
||||||
void sendMessage(const EmulatorMessage& message);
|
void sendMessage(const EmulatorMessage& message);
|
||||||
void dispatchMessage(const EmulatorMessage& message);
|
void dispatchMessage(const EmulatorMessage& message);
|
||||||
|
|
||||||
|
@ -111,6 +119,12 @@ class MainWindow : public QMainWindow {
|
||||||
bool usingGL = false;
|
bool usingGL = false;
|
||||||
bool usingVk = false;
|
bool usingVk = false;
|
||||||
|
|
||||||
|
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
|
||||||
|
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state
|
||||||
|
// And so the user can still use the keyboard to control the analog
|
||||||
|
bool keyboardAnalogX = false;
|
||||||
|
bool keyboardAnalogY = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainWindow(QApplication* app, QWidget* parent = nullptr);
|
MainWindow(QApplication* app, QWidget* parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
|
@ -97,6 +97,8 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
Helpers::panic("Unsupported graphics backend for Qt frontend!");
|
Helpers::panic("Unsupported graphics backend for Qt frontend!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have to initialize controllers on the same thread they'll be polled in
|
||||||
|
initControllers();
|
||||||
emuThreadMainLoop();
|
emuThreadMainLoop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -117,6 +119,8 @@ void MainWindow::emuThreadMainLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
emu->runFrame();
|
emu->runFrame();
|
||||||
|
pollControllers();
|
||||||
|
|
||||||
if (emu->romType != ROMType::None) {
|
if (emu->romType != ROMType::None) {
|
||||||
emu->getServiceManager().getHID().updateInputs(emu->getTicks());
|
emu->getServiceManager().getHID().updateInputs(emu->getTicks());
|
||||||
}
|
}
|
||||||
|
@ -279,8 +283,21 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||||
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
||||||
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
||||||
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
||||||
case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break;
|
|
||||||
case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break;
|
// Track whether we're controlling the analog stick with our controller and update the CirclePad X/Y values in HID
|
||||||
|
// Controllers are polled on the emulator thread, so this message type is only used when the circlepad is changed via keyboard input
|
||||||
|
case MessageType::SetCirclePadX: {
|
||||||
|
keyboardAnalogX = message.circlepad.value != 0;
|
||||||
|
emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MessageType::SetCirclePadY: {
|
||||||
|
keyboardAnalogY = message.circlepad.value != 0;
|
||||||
|
emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case MessageType::PressTouchscreen:
|
case MessageType::PressTouchscreen:
|
||||||
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
|
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
|
||||||
break;
|
break;
|
||||||
|
@ -398,3 +415,99 @@ void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const
|
||||||
message.cheat.c = c;
|
message.cheat.c = c;
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::initControllers() {
|
||||||
|
// Make SDL use consistent positional button mapping
|
||||||
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
||||||
|
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) {
|
||||||
|
Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
||||||
|
gameController = SDL_GameControllerOpen(0);
|
||||||
|
|
||||||
|
if (gameController != nullptr) {
|
||||||
|
SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController);
|
||||||
|
gameControllerID = SDL_JoystickInstanceID(stick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::pollControllers() {
|
||||||
|
// Update circlepad if a controller is plugged in
|
||||||
|
if (gameController != nullptr) {
|
||||||
|
HIDService& hid = emu->getServiceManager().getHID();
|
||||||
|
const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
constexpr s16 deadzone = 3276;
|
||||||
|
constexpr s16 maxValue = 0x9C;
|
||||||
|
constexpr s16 div = 0x8000 / maxValue;
|
||||||
|
|
||||||
|
// Avoid overriding the keyboard's circlepad input
|
||||||
|
if (std::abs(stickX) < deadzone && !keyboardAnalogX) {
|
||||||
|
hid.setCirclepadX(0);
|
||||||
|
} else {
|
||||||
|
hid.setCirclepadX(stickX / div);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(stickY) < deadzone && !keyboardAnalogY) {
|
||||||
|
hid.setCirclepadY(0);
|
||||||
|
} else {
|
||||||
|
hid.setCirclepadY(-(stickY / div));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
HIDService& hid = emu->getServiceManager().getHID();
|
||||||
|
using namespace HID;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
|
if (gameController == nullptr) {
|
||||||
|
gameController = SDL_GameControllerOpen(event.cdevice.which);
|
||||||
|
gameControllerID = event.cdevice.which;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
if (event.cdevice.which == gameControllerID) {
|
||||||
|
SDL_GameControllerClose(gameController);
|
||||||
|
gameController = nullptr;
|
||||||
|
gameControllerID = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
|
case SDL_CONTROLLERBUTTONDOWN: {
|
||||||
|
if (emu->romType == ROMType::None) break;
|
||||||
|
u32 key = 0;
|
||||||
|
|
||||||
|
switch (event.cbutton.button) {
|
||||||
|
case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != 0) {
|
||||||
|
if (event.cbutton.state == SDL_PRESSED) {
|
||||||
|
hid.pressKey(key);
|
||||||
|
} else {
|
||||||
|
hid.releaseKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue