mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-04-02 11:17:43 -06:00
analogs working (and various cleanups)
This commit is contained in:
parent
daa37dfd99
commit
20d8bc6320
@ -95,6 +95,7 @@ void ConfigureControllerHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
|
||||
if (action_name != action->text())
|
||||
continue;
|
||||
hotkey.controller_keyseq = controller_keyseq->text();
|
||||
registry.UpdateControllerHotkey(action_name, hotkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,6 +148,8 @@ QString ConfigureControllerHotkeys::CleanSequence(QString controller_keyseq) {
|
||||
output = QString::fromStdString("Hat " + p1.Get("hat", "") + " " + p1.Get("direction", ""));
|
||||
} else if (p1.Has("button")) {
|
||||
output = QString::fromStdString("Button " + p1.Get("button", ""));
|
||||
} else if (p1.Has("axis")) {
|
||||
output += QString::fromStdString("Axis " + p1.Get("axis", "") + p1.Get("direction", ""));
|
||||
}
|
||||
|
||||
if (keys.length() > 1) {
|
||||
@ -157,6 +160,9 @@ QString ConfigureControllerHotkeys::CleanSequence(QString controller_keyseq) {
|
||||
QString::fromStdString("Hat " + p1.Get("hat", "") + " " + p1.Get("direction", ""));
|
||||
} else if (p1.Has("button")) {
|
||||
output += QString::fromStdString("Button " + p1.Get("button", ""));
|
||||
} else if (p1.Has("axis")) {
|
||||
output +=
|
||||
QString::fromStdString("Axis " + p1.Get("axis", "") + p1.Get("direction", ""));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
|
||||
@ -407,8 +407,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
|
||||
Common::ParamPackage params;
|
||||
for (auto& poller : device_pollers) {
|
||||
params = poller->GetNextInput();
|
||||
// skip button downs and only process button ups to maintain former behavior
|
||||
if (params.Has("engine") && !params.Has("down")) {
|
||||
if (params.Has("engine")) {
|
||||
SetPollingResult(params, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,6 +45,10 @@ void ControllerHotkeyMonitor::checkAllButtons() {
|
||||
if (it.hk->button_device2) {
|
||||
// two buttons, need both pressed and one *just now* pressed
|
||||
bool currentStatus2 = it.hk->button_device2->GetStatus();
|
||||
if (currentStatus)
|
||||
std::cerr << "button one pressed" << std::endl;
|
||||
if (currentStatus2)
|
||||
std::cerr << "button two pressed" << std::endl;
|
||||
trigger = currentStatus && currentStatus2 && (!it.lastStatus || !it.lastStatus2);
|
||||
it.lastStatus = currentStatus;
|
||||
it.lastStatus2 = currentStatus2;
|
||||
|
||||
@ -24,7 +24,22 @@ void HotkeyRegistry::SaveHotkeys() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyRegistry::UpdateControllerHotkey(QString name, Hotkey& hk) {
|
||||
if (hk.controller_keyseq.isEmpty()) {
|
||||
buttonMonitor.removeButton(name);
|
||||
} else {
|
||||
QStringList paramList = hk.controller_keyseq.split(QStringLiteral("||"));
|
||||
if (paramList.length() > 0) {
|
||||
hk.button_device =
|
||||
Input::CreateDevice<Input::ButtonDevice>(paramList.value(0).toStdString());
|
||||
if (paramList.length() > 1) {
|
||||
hk.button_device2 =
|
||||
Input::CreateDevice<Input::ButtonDevice>(paramList.value(1).toStdString());
|
||||
}
|
||||
buttonMonitor.addButton(name, &hk);
|
||||
}
|
||||
}
|
||||
}
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
@ -36,18 +51,8 @@ void HotkeyRegistry::LoadHotkeys() {
|
||||
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
|
||||
hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
|
||||
}
|
||||
if (!hk.controller_keyseq.isEmpty()) {
|
||||
QStringList paramList = hk.controller_keyseq.split(QStringLiteral("||"));
|
||||
if (paramList.length() > 0) {
|
||||
hk.button_device =
|
||||
Input::CreateDevice<Input::ButtonDevice>(paramList.at(0).toStdString());
|
||||
if (paramList.length() > 1) {
|
||||
hk.button_device2 =
|
||||
Input::CreateDevice<Input::ButtonDevice>(paramList.at(1).toStdString());
|
||||
}
|
||||
buttonMonitor.addButton(shortcut.name, &hk);
|
||||
}
|
||||
}
|
||||
UpdateControllerHotkey(shortcut.name, hk);
|
||||
|
||||
for (auto const& [_, hotkey_shortcut] : hk.shortcuts) {
|
||||
if (hotkey_shortcut) {
|
||||
hotkey_shortcut->disconnect();
|
||||
|
||||
@ -52,6 +52,11 @@ public:
|
||||
*/
|
||||
void SaveHotkeys();
|
||||
|
||||
/**
|
||||
* Updates the button devices for a hotkey based on the controller_keyseq value
|
||||
*/
|
||||
void UpdateControllerHotkey(QString name, Hotkey& hk);
|
||||
|
||||
/**
|
||||
* Returns a QShortcut object whose activated() signal can be connected to other QObjects'
|
||||
* slots.
|
||||
@ -59,8 +64,8 @@ public:
|
||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
|
||||
* @param widget Parent widget of the returned QShortcut.
|
||||
* @warning If multiple QWidgets' call this function for the same action, the returned QShortcut
|
||||
* will be the same. Thus, you shouldn't rely on the caller really being the
|
||||
* @warning If multiple QWidgets' call this function for the same action, the returned
|
||||
* QShortcut will be the same. Thus, you shouldn't rely on the caller really being the
|
||||
* QShortcut's parent.
|
||||
*/
|
||||
QShortcut* GetHotkey(const QString& group, const QString& action, QObject* widget);
|
||||
|
||||
@ -27,7 +27,7 @@ ControllerSequenceDialog::ControllerSequenceDialog(QWidget* parent)
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
LaunchPollers(); // Fixed: added semicolon
|
||||
LaunchPollers();
|
||||
}
|
||||
|
||||
ControllerSequenceDialog::~ControllerSequenceDialog() = default;
|
||||
@ -55,8 +55,7 @@ void ControllerSequenceDialog::LaunchPollers() {
|
||||
Common::ParamPackage params;
|
||||
for (auto& poller : device_pollers) {
|
||||
params = poller->GetNextInput();
|
||||
if (params.Has("engine") &&
|
||||
(params.Has("hat") || params.Has("button"))) { // for now, no analog inputs
|
||||
if (params.Has("engine")) {
|
||||
std::cerr << "controller hotkey event detected: " + params.Serialize() << std::endl;
|
||||
if (params.Has("down")) {
|
||||
downCount++;
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@ -187,6 +188,7 @@ public:
|
||||
state.buttons[button] = value;
|
||||
}
|
||||
|
||||
// TODO: remove if all goes well
|
||||
// no longer used - creating state breaks hotkey detection, so
|
||||
// poll sdl directly below
|
||||
bool GetButton(int button) const {
|
||||
@ -207,11 +209,18 @@ public:
|
||||
state.axes[axis] = value;
|
||||
}
|
||||
|
||||
// TODO: remove if all goes well
|
||||
float GetAxis(int axis) const {
|
||||
std::lock_guard lock{mutex};
|
||||
return state.axes.at(axis) / 32767.0f;
|
||||
}
|
||||
|
||||
float GetAxisDirect(int axis) const {
|
||||
if (!sdl_joystick)
|
||||
return 0.0;
|
||||
return SDL_JoystickGetAxis(sdl_joystick.get(), axis) / 32767.0f;
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
|
||||
float x = GetAxis(axis_x);
|
||||
float y = GetAxis(axis_y);
|
||||
@ -235,6 +244,7 @@ public:
|
||||
}
|
||||
|
||||
// no longer used, poll directly instead
|
||||
// TODO: remove if all goes well
|
||||
bool GetHatDirection(int hat, Uint8 direction) const {
|
||||
std::lock_guard lock{mutex};
|
||||
return (state.hats.at(hat) & direction) != 0;
|
||||
@ -661,7 +671,7 @@ public:
|
||||
trigger_if_greater(trigger_if_greater_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
float axis_value = joystick->GetAxis(axis);
|
||||
float axis_value = joystick->GetAxisDirect(axis);
|
||||
if (trigger_if_greater)
|
||||
return axis_value > threshold;
|
||||
return axis_value < threshold;
|
||||
@ -968,6 +978,7 @@ public:
|
||||
|
||||
void Start() override {
|
||||
state.event_queue.Clear();
|
||||
|
||||
state.polling = true;
|
||||
}
|
||||
|
||||
@ -985,56 +996,92 @@ public:
|
||||
|
||||
Common::ParamPackage GetNextInput() override {
|
||||
SDL_Event event;
|
||||
bool down = false;
|
||||
while (state.event_queue.Pop(event)) {
|
||||
auto axis = event.jaxis.axis;
|
||||
auto id = event.jaxis.which;
|
||||
auto value = event.jaxis.value;
|
||||
switch (event.type) {
|
||||
case SDL_JOYAXISMOTION:
|
||||
if (!axis_memory.count(event.jaxis.which) ||
|
||||
!axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
|
||||
axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
|
||||
axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
|
||||
if (IsAxisAtPole(event.jaxis.value)) {
|
||||
down = true;
|
||||
} else {
|
||||
break;
|
||||
std::cerr << "axis" << event.jaxis.axis << " down event, has value "
|
||||
<< event.jaxis.value << " and timestamp " << event.jaxis.timestamp
|
||||
<< std::endl;
|
||||
// if a button has been pressed down within 50ms of this axis movement,
|
||||
// assume they are actually the same thing and skip this axis
|
||||
if (buttonDownTimestamp && ((event.jaxis.timestamp >= buttonDownTimestamp &&
|
||||
event.jaxis.timestamp - buttonDownTimestamp <= 50) ||
|
||||
(event.jaxis.timestamp < buttonDownTimestamp &&
|
||||
buttonDownTimestamp - event.jaxis.timestamp <= 50))) {
|
||||
axis_skip[id][axis] = true;
|
||||
std::cerr << "skipping axis " << axis
|
||||
<< " because a button happened simultaneously";
|
||||
break;
|
||||
}
|
||||
|
||||
// skipping this axis
|
||||
if (axis_skip[id][axis])
|
||||
break;
|
||||
if (!axis_memory.count(id) || !axis_memory[id].count(axis)) {
|
||||
// starting a new movement.
|
||||
axisStartTimestamps[id][axis] = event.jaxis.timestamp;
|
||||
axis_event_count[id][axis] = 1;
|
||||
if (IsAxisAtExtreme(value)) {
|
||||
// a single event with a value right at the extreme.
|
||||
// Assume this is a digital "axis" and send the down
|
||||
// signal with center set to 0.
|
||||
event.jaxis.value = std::copysign(32767, value);
|
||||
axis_center_value[id][axis] = 0;
|
||||
return SDLEventToButtonParamPackage(state, event, true);
|
||||
}
|
||||
// otherwise, this is our first event, identify the center
|
||||
if (value < -28000)
|
||||
axis_center_value[id][axis] = -32768;
|
||||
else if (value > 28000)
|
||||
axis_center_value[id][axis] = 32767;
|
||||
else
|
||||
axis_center_value[id][axis] = 0;
|
||||
|
||||
axis_memory[id][axis] = axis_center_value[id][axis];
|
||||
break;
|
||||
} else {
|
||||
axis_event_count[event.jaxis.which][event.jaxis.axis]++;
|
||||
// The joystick and axis exist in our map if we take this branch, so no checks
|
||||
// needed
|
||||
if (std::abs(
|
||||
(event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
|
||||
32767.0) < 0.5) {
|
||||
break;
|
||||
} else {
|
||||
if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
|
||||
IsAxisAtPole(event.jaxis.value) &&
|
||||
IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
|
||||
// If we have exactly two events and both are near a pole, this is
|
||||
// likely a digital input masquerading as an analog axis; Instead of
|
||||
// trying to look at the direction the axis travelled, assume the first
|
||||
// event was press and the second was release; This should handle most
|
||||
// digital axes while deferring to the direction of travel for analog
|
||||
// axes
|
||||
event.jaxis.value = static_cast<Sint16>(std::copysign(
|
||||
32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
|
||||
} else {
|
||||
// There are more than two events, so this is likely a true analog axis,
|
||||
// check the direction it travelled
|
||||
event.jaxis.value = static_cast<Sint16>(std::copysign(
|
||||
32767, event.jaxis.value -
|
||||
axis_memory[event.jaxis.which][event.jaxis.axis]));
|
||||
}
|
||||
axis_memory.clear();
|
||||
axis_event_count.clear();
|
||||
axis_event_count[id][axis]++;
|
||||
// only two events, second one at center, means this is a digital release
|
||||
if (axis_event_count[id][axis] == 2 && IsAxisAtCenter(value, id, axis) &&
|
||||
IsAxisAtExtreme(axis_memory[id][axis])) {
|
||||
// send the up signal for this digital axis, and clear.
|
||||
axis_event_count[id][axis] = 0;
|
||||
axis_memory[id][axis] = 0;
|
||||
return SDLEventToButtonParamPackage(state, event, false);
|
||||
}
|
||||
if (IsAxisAtCenter(value, id, axis) &&
|
||||
IsAxisPastThreshold(axis_memory[id][axis], id, axis)) {
|
||||
// returned to center, send the up signal
|
||||
event.jaxis.value = static_cast<Sint16>(std::copysign(
|
||||
32767, axis_memory[id][axis] - axis_center_value[id][axis]));
|
||||
axis_memory[id][axis] = 0;
|
||||
axis_event_count[id][axis] = 0;
|
||||
std::cerr << "sending up signal for axis " << axis;
|
||||
return SDLEventToButtonParamPackage(state, event, false);
|
||||
} else if (IsAxisAtCenter(axis_memory[id][axis], id, axis) &&
|
||||
IsAxisPastThreshold(event.jaxis.value, id, axis)) {
|
||||
event.jaxis.value = static_cast<Sint16>(
|
||||
std::copysign(32767, event.jaxis.value - axis_center_value[id][axis]));
|
||||
// make this the start of the new event
|
||||
axis_memory[id][axis] = event.jaxis.value;
|
||||
std::cerr << "sending down signal for axis " << axis;
|
||||
return SDLEventToButtonParamPackage(state, event, true);
|
||||
}
|
||||
}
|
||||
return SDLEventToButtonParamPackage(state, event, down);
|
||||
break;
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
std::cerr << "button " << event.jbutton.button << " down event, has axis "
|
||||
<< event.jaxis.axis << " and timestamp " << event.jbutton.timestamp
|
||||
<< std::endl;
|
||||
buttonDownTimestamp = event.jbutton.timestamp;
|
||||
return SDLEventToButtonParamPackage(state, event, true);
|
||||
break;
|
||||
case SDL_JOYBUTTONUP:
|
||||
std::cerr << "button " << event.jbutton.button << " up event, timestamp "
|
||||
<< event.jbutton.timestamp;
|
||||
return SDLEventToButtonParamPackage(state, event, false);
|
||||
break;
|
||||
case SDL_JOYHATMOTION:
|
||||
@ -1047,14 +1094,28 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Determine whether an axis value is close to an extreme or center
|
||||
// Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
|
||||
// axis, which is why the center must be considered a pole
|
||||
bool IsAxisAtPole(int16_t value) {
|
||||
return std::abs(value) >= 32767 || std::abs(value) < 327;
|
||||
bool IsAxisAtCenter(int16_t value, SDL_JoystickID id, uint8_t axis) {
|
||||
return std::abs(value - axis_center_value[id][axis]) < 367;
|
||||
}
|
||||
|
||||
bool IsAxisPastThreshold(int16_t value, SDL_JoystickID id, uint8_t axis) {
|
||||
return std::abs(value - axis_center_value[id][axis]) > 32767 / 2;
|
||||
}
|
||||
|
||||
bool IsAxisAtExtreme(int16_t value) {
|
||||
return std::abs(value) > 32766;
|
||||
}
|
||||
|
||||
bool IsValueNear(int16_t value1, int16_t value2) {}
|
||||
/** Holds the first received value for the axis. Used to
|
||||
* identify situations where "released" is -32768 (some triggers)
|
||||
*/
|
||||
std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_center_value;
|
||||
std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
|
||||
std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
|
||||
std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, bool>> axis_skip;
|
||||
int buttonDownTimestamp = 0;
|
||||
std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int>> axisStartTimestamps;
|
||||
};
|
||||
|
||||
class SDLAnalogPoller final : public SDLPoller {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user