mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
rawinput: modifiers (key combinations) (#813)
## Link to GitHub Issue or related Pull Request, if one exists Fixes #635 ## Description of change Adds optional **button modifiers**, allowing a binding to require one or more "modifier" buttons to be held before it activates. - New "Modifiers" controller page in the overlay config to bind the Modifier 1–4 source buttons. - Each binding gains a 4-bit modifier_mask (Modifier 1-4). It is persisted as an optional modifiers XML attribute on button nodes and controller-preset entries. The attribute is optional and defaults to 0, so existing config files and presets load unchanged and the feature is off unless the user opts in. - The button Edit properties popup gains a "Modifiers" dropdown to pick which modifiers a binding requires. Doesn't apply to MIDI though. - Input evaluation skips a binding whose required modifiers are not held, falling through to its alternatives; velocity reporting uses the same gated path. - Controller-preset templates gain a "Modifiers" group for importing and exporting. ## Testing
This commit is contained in:
+66
-11
@@ -3,7 +3,9 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include "games/io.h"
|
||||||
#include "launcher/superexit.h"
|
#include "launcher/superexit.h"
|
||||||
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "rawinput/piuio.h"
|
#include "rawinput/piuio.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
@@ -62,7 +64,43 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *manager, Button &_button, bool check_alts) {
|
namespace GameAPI::Buttons {
|
||||||
|
static State get_button_state(
|
||||||
|
rawinput::RawInputManager *manager,
|
||||||
|
Button &button,
|
||||||
|
bool check_alts,
|
||||||
|
bool check_modifiers);
|
||||||
|
|
||||||
|
static bool modifiers_pressed(rawinput::RawInputManager *manager, Button &button);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameAPI::Buttons::modifiers_pressed(rawinput::RawInputManager *manager, Button &button) {
|
||||||
|
const auto modifier_mask = button.getModifierMask();
|
||||||
|
if (modifier_mask == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(eamuse_get_game());
|
||||||
|
if (!modifier_buttons) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
if ((modifier_mask & (UINT8_C(1) << index)) != 0 &&
|
||||||
|
(index >= modifier_buttons->size() ||
|
||||||
|
get_button_state(manager, modifier_buttons->at(index), true, false) !=
|
||||||
|
GameAPI::Buttons::BUTTON_PRESSED)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameAPI::Buttons::State GameAPI::Buttons::get_button_state(
|
||||||
|
rawinput::RawInputManager *manager,
|
||||||
|
Button &_button,
|
||||||
|
bool check_alts,
|
||||||
|
bool check_modifiers) {
|
||||||
|
|
||||||
// check override
|
// check override
|
||||||
if (_button.override_enabled) {
|
if (_button.override_enabled) {
|
||||||
@@ -76,6 +114,23 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
std::optional<bool> window_has_focus;
|
std::optional<bool> window_has_focus;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
|
// skip bindings whose required modifiers are not held
|
||||||
|
//
|
||||||
|
// note that modifiers cannot process MIDI as the logic is written
|
||||||
|
// below, since MIDI buttons are event-based (on event, off event, etc)
|
||||||
|
// and cannot be correctly handled by a simple early return
|
||||||
|
// there is no explicit check for MIDI here, but the UI should have
|
||||||
|
// prevented it
|
||||||
|
if (check_modifiers && !modifiers_pressed(manager, *current_button)) {
|
||||||
|
button_count++;
|
||||||
|
if (!alternatives || alternatives->empty() ||
|
||||||
|
button_count - 1 >= alternatives->size()) {
|
||||||
|
return BUTTON_NOT_PRESSED;
|
||||||
|
}
|
||||||
|
current_button = &alternatives->at(button_count - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// naive behavior
|
// naive behavior
|
||||||
if (current_button->isNaive()) {
|
if (current_button->isNaive()) {
|
||||||
GameAPI::Buttons::State state;
|
GameAPI::Buttons::State state;
|
||||||
@@ -469,6 +524,10 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Buttons::State Buttons::getState(rawinput::RawInputManager *manager, Button &button, bool check_alts) {
|
||||||
|
return get_button_state(manager, button, check_alts, true);
|
||||||
|
}
|
||||||
|
|
||||||
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
|
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
|
||||||
if (manager) {
|
if (manager) {
|
||||||
return getState(manager.get(), button, check_alts);
|
return getState(manager.get(), button, check_alts);
|
||||||
@@ -484,17 +543,13 @@ static float getVelocityHelper(rawinput::RawInputManager *manager, Button &butto
|
|||||||
return button.override_velocity;
|
return button.override_velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// naive behavior
|
|
||||||
if (button.isNaive()) {
|
|
||||||
if (button.getInvert()) {
|
|
||||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 0.f : 1.f;
|
|
||||||
} else {
|
|
||||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 1.f : 0.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get button state
|
// get button state
|
||||||
Buttons::State button_state = Buttons::getState(manager, button, false);
|
const auto button_state = Buttons::getState(manager, button, false);
|
||||||
|
|
||||||
|
// naive bindings report their digital state as full or zero velocity
|
||||||
|
if (button.isNaive()) {
|
||||||
|
return button_state == Buttons::BUTTON_PRESSED ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
// check if button isn't being pressed
|
// check if button isn't being pressed
|
||||||
if (button_state != Buttons::BUTTON_PRESSED) {
|
if (button_state != Buttons::BUTTON_PRESSED) {
|
||||||
|
|||||||
@@ -299,7 +299,27 @@ std::string Button::getMidiNoteString() {
|
|||||||
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
|
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
std::string Button::getDisplayString(rawinput::RawInputManager *manager) {
|
||||||
|
auto binding_display = this->getBindingDisplayString(manager);
|
||||||
|
auto modifiers = this->getModifierMask();
|
||||||
|
if (binding_display.empty() || modifiers == 0) {
|
||||||
|
return binding_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string modifier_display;
|
||||||
|
for (unsigned int index = 1; modifiers != 0; index++, modifiers >>= 1) {
|
||||||
|
if ((modifiers & UINT8_C(1)) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!modifier_display.empty()) {
|
||||||
|
modifier_display += "+";
|
||||||
|
}
|
||||||
|
modifier_display += fmt::format("Mod{}", index);
|
||||||
|
}
|
||||||
|
return modifier_display + "+" + binding_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Button::getBindingDisplayString(rawinput::RawInputManager *manager) {
|
||||||
|
|
||||||
// get VKey string
|
// get VKey string
|
||||||
auto vKey = (uint16_t) this->getVKey();
|
auto vKey = (uint16_t) this->getVKey();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ private:
|
|||||||
std::string name;
|
std::string name;
|
||||||
std::string device_identifier = "";
|
std::string device_identifier = "";
|
||||||
unsigned short vKey = INVALID_VKEY;
|
unsigned short vKey = INVALID_VKEY;
|
||||||
|
uint8_t modifier_mask = 0;
|
||||||
|
|
||||||
// default bindings are always naive
|
// default bindings are always naive
|
||||||
unsigned short vKey_default = INVALID_VKEY;
|
unsigned short vKey_default = INVALID_VKEY;
|
||||||
@@ -65,6 +66,7 @@ private:
|
|||||||
unsigned short velocity_threshold = 0;
|
unsigned short velocity_threshold = 0;
|
||||||
|
|
||||||
std::string getMidiNoteString();
|
std::string getMidiNoteString();
|
||||||
|
std::string getBindingDisplayString(rawinput::RawInputManager *manager);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string getVKeyString();
|
std::string getVKeyString();
|
||||||
@@ -111,6 +113,7 @@ public:
|
|||||||
device_identifier = "";
|
device_identifier = "";
|
||||||
analog_type = BAT_NONE;
|
analog_type = BAT_NONE;
|
||||||
bat_threshold = 0;
|
bat_threshold = 0;
|
||||||
|
modifier_mask = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||||
@@ -123,6 +126,14 @@ public:
|
|||||||
return this->name;
|
return this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint8_t getModifierMask() const {
|
||||||
|
return this->modifier_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void setModifierMask(uint8_t new_modifier_mask) {
|
||||||
|
this->modifier_mask = new_modifier_mask & UINT8_C(0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
inline const std::string &getDeviceIdentifier() const {
|
inline const std::string &getDeviceIdentifier() const {
|
||||||
return this->device_identifier;
|
return this->device_identifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
const char *devid = gameButtonNode->Attribute("devid");
|
const char *devid = gameButtonNode->Attribute("devid");
|
||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
@@ -173,6 +174,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
|
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||||
gameButtonsNode->DeleteChild(gameButtonNode);
|
gameButtonsNode->DeleteChild(gameButtonNode);
|
||||||
gameButtonNode = this->configFile.NewElement("button");
|
gameButtonNode = this->configFile.NewElement("button");
|
||||||
@@ -185,6 +187,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||||
gameButtonNode->SetAttribute("invert", invert);
|
gameButtonNode->SetAttribute("invert", invert);
|
||||||
|
gameButtonNode->SetAttribute("modifiers", button->getModifierMask());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
} else {
|
} else {
|
||||||
button->setVKey(static_cast<unsigned short int>(vKey));
|
button->setVKey(static_cast<unsigned short int>(vKey));
|
||||||
@@ -194,6 +197,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
button->setBatThreshold(bat_threshold);
|
button->setBatThreshold(bat_threshold);
|
||||||
button->setVelocityThreshold(velocity_threshold);
|
button->setVelocityThreshold(velocity_threshold);
|
||||||
button->setInvert(invert);
|
button->setInvert(invert);
|
||||||
|
button->setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
button->setDeviceIdentifier(devid);
|
button->setDeviceIdentifier(devid);
|
||||||
}
|
}
|
||||||
@@ -214,6 +218,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
@@ -440,6 +445,7 @@ bool Config::addGame(Game &game, bool save) {
|
|||||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
@@ -552,6 +558,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||||
|
gameButtonNode->SetAttribute("modifiers", button.getModifierMask());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,6 +576,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||||
gameButtonNode->SetAttribute("invert", false);
|
gameButtonNode->SetAttribute("invert", false);
|
||||||
gameButtonNode->SetAttribute("devid", "");
|
gameButtonNode->SetAttribute("devid", "");
|
||||||
|
gameButtonNode->SetAttribute("modifiers", 0);
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -964,6 +972,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||||
@@ -971,6 +980,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
|
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
const char *devid = gameButtonNode->Attribute("devid");
|
const char *devid = gameButtonNode->Attribute("devid");
|
||||||
|
|
||||||
// find alternative
|
// find alternative
|
||||||
@@ -986,6 +996,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
alt.setBatThreshold(bat_threshold);
|
alt.setBatThreshold(bat_threshold);
|
||||||
alt.setVelocityThreshold(velocity_threshold);
|
alt.setVelocityThreshold(velocity_threshold);
|
||||||
alt.setInvert(invert);
|
alt.setInvert(invert);
|
||||||
|
alt.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
alt.setDeviceIdentifier(std::string(devid));
|
alt.setDeviceIdentifier(std::string(devid));
|
||||||
}
|
}
|
||||||
@@ -1005,6 +1016,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
button.setBatThreshold(bat_threshold);
|
button.setBatThreshold(bat_threshold);
|
||||||
button.setVelocityThreshold(velocity_threshold);
|
button.setVelocityThreshold(velocity_threshold);
|
||||||
button.setInvert(invert);
|
button.setInvert(invert);
|
||||||
|
button.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||||
if (devid) {
|
if (devid) {
|
||||||
button.setDeviceIdentifier(devid);
|
button.setDeviceIdentifier(devid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
|||||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||||
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||||
|
el->SetAttribute("modifiers", entry.modifier_mask);
|
||||||
parent->InsertEndChild(el);
|
parent->InsertEndChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,10 @@ namespace overlay::windows {
|
|||||||
el->QueryIntAttribute("bat_threshold", &bat);
|
el->QueryIntAttribute("bat_threshold", &bat);
|
||||||
entry.bat_threshold = bat;
|
entry.bat_threshold = bat;
|
||||||
|
|
||||||
|
unsigned int modifier_mask = 0;
|
||||||
|
el->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||||
|
entry.modifier_mask = static_cast<uint8_t>(modifier_mask & UINT8_C(0x0F));
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +165,28 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifier buttons
|
||||||
|
auto *mod_el = tmpl_el->FirstChildElement("modifier_buttons");
|
||||||
|
if (mod_el) {
|
||||||
|
auto *btn_el = mod_el->FirstChildElement("button");
|
||||||
|
while (btn_el) {
|
||||||
|
const char *btn_name = btn_el->Attribute("name");
|
||||||
|
std::string btn_name_str = btn_name ? btn_name : "";
|
||||||
|
|
||||||
|
TemplateButtonBinding *binding = nullptr;
|
||||||
|
for (auto &b : tmpl.modifier_buttons) {
|
||||||
|
if (b.name == btn_name_str) { binding = &b; break; }
|
||||||
|
}
|
||||||
|
if (!binding) {
|
||||||
|
tmpl.modifier_buttons.push_back(
|
||||||
|
{btn_name_str, read_button_entry(btn_el), {}});
|
||||||
|
} else {
|
||||||
|
binding->alternatives.push_back(read_button_entry(btn_el));
|
||||||
|
}
|
||||||
|
btn_el = btn_el->NextSiblingElement("button");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons
|
// keypad buttons
|
||||||
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
||||||
if (kp_el) {
|
if (kp_el) {
|
||||||
@@ -244,6 +271,7 @@ namespace overlay::windows {
|
|||||||
bool save_user_template(
|
bool save_user_template(
|
||||||
const ControllerTemplate &tmpl,
|
const ControllerTemplate &tmpl,
|
||||||
const bool save_buttons,
|
const bool save_buttons,
|
||||||
|
const bool save_modifiers,
|
||||||
const bool save_keypads,
|
const bool save_keypads,
|
||||||
const bool save_analogs,
|
const bool save_analogs,
|
||||||
const bool save_lights) {
|
const bool save_lights) {
|
||||||
@@ -300,6 +328,24 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
tmpl_el->InsertEndChild(buttons_el);
|
tmpl_el->InsertEndChild(buttons_el);
|
||||||
|
|
||||||
|
// modifier buttons - skip unbound entries
|
||||||
|
if (!tmpl.modifier_buttons.empty()) {
|
||||||
|
auto *mod_el = doc.NewElement("modifier_buttons");
|
||||||
|
if (save_modifiers) {
|
||||||
|
for (auto &btn : tmpl.modifier_buttons) {
|
||||||
|
if (!btn.primary.is_unbound()) {
|
||||||
|
write_button_entry(doc, mod_el, btn.name, btn.primary);
|
||||||
|
}
|
||||||
|
for (auto &alt : btn.alternatives) {
|
||||||
|
if (!alt.is_unbound()) {
|
||||||
|
write_button_entry(doc, mod_el, btn.name, alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmpl_el->InsertEndChild(mod_el);
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons — skip unbound entries
|
// keypad buttons — skip unbound entries
|
||||||
if (!tmpl.keypad_buttons.empty()) {
|
if (!tmpl.keypad_buttons.empty()) {
|
||||||
auto *kp_el = doc.NewElement("keypad_buttons");
|
auto *kp_el = doc.NewElement("keypad_buttons");
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace games {
|
|||||||
static bool IO_INITIALIZED = false;
|
static bool IO_INITIALIZED = false;
|
||||||
static std::vector<std::string> games;
|
static std::vector<std::string> games;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
|
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
|
||||||
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_modifiers;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
||||||
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
||||||
@@ -365,6 +366,37 @@ namespace games {
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<Button> gen_buttons_modifiers(const std::string &game) {
|
||||||
|
auto modifier_buttons = GameAPI::Buttons::getButtons(game);
|
||||||
|
const std::vector<std::string> names {
|
||||||
|
"Modifier 1",
|
||||||
|
"Modifier 2",
|
||||||
|
"Modifier 3",
|
||||||
|
"Modifier 4",
|
||||||
|
};
|
||||||
|
modifier_buttons = GameAPI::Buttons::sortButtons(modifier_buttons, names);
|
||||||
|
for (auto &modifier : modifier_buttons) {
|
||||||
|
modifier.setModifierMask(0);
|
||||||
|
for (auto &alternative : modifier.getAlternatives()) {
|
||||||
|
alternative.setModifierMask(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modifier_buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Button> *get_buttons_modifiers(const std::string &game) {
|
||||||
|
initialize();
|
||||||
|
auto it = buttons_modifiers.find(game);
|
||||||
|
if (it == buttons_modifiers.end()) {
|
||||||
|
if (game.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
buttons_modifiers[game] = gen_buttons_modifiers(game);
|
||||||
|
return &buttons_modifiers[game];
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
||||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
|
|||||||
@@ -5,6 +5,16 @@
|
|||||||
|
|
||||||
namespace games {
|
namespace games {
|
||||||
|
|
||||||
|
namespace ModifierButtons {
|
||||||
|
enum {
|
||||||
|
Modifier1,
|
||||||
|
Modifier2,
|
||||||
|
Modifier3,
|
||||||
|
Modifier4,
|
||||||
|
Size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
namespace OverlayButtons {
|
namespace OverlayButtons {
|
||||||
enum {
|
enum {
|
||||||
Screenshot,
|
Screenshot,
|
||||||
@@ -59,6 +69,7 @@ namespace games {
|
|||||||
|
|
||||||
const std::vector<std::string> &get_games();
|
const std::vector<std::string> &get_games();
|
||||||
std::vector<Button> *get_buttons(const std::string &game);
|
std::vector<Button> *get_buttons(const std::string &game);
|
||||||
|
std::vector<Button> *get_buttons_modifiers(const std::string &game);
|
||||||
std::string get_buttons_help(const std::string &game);
|
std::string get_buttons_help(const std::string &game);
|
||||||
std::string get_analogs_help(const std::string &game);
|
std::string get_analogs_help(const std::string &game);
|
||||||
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ namespace overlay::windows {
|
|||||||
static const std::vector<std::pair<const char *, ControllerPage>> CONTROLLER_PAGE_GROUPS = {
|
static const std::vector<std::pair<const char *, ControllerPage>> CONTROLLER_PAGE_GROUPS = {
|
||||||
{ "Buttons", ControllerPage::CONTROLLER_PAGE_BUTTONS },
|
{ "Buttons", ControllerPage::CONTROLLER_PAGE_BUTTONS },
|
||||||
{ "Keypads", ControllerPage::CONTROLLER_PAGE_KEYPADS },
|
{ "Keypads", ControllerPage::CONTROLLER_PAGE_KEYPADS },
|
||||||
|
{ "Modifiers", ControllerPage::CONTROLLER_PAGE_MODIFIERS },
|
||||||
{ "Analogs", ControllerPage::CONTROLLER_PAGE_ANALOGS },
|
{ "Analogs", ControllerPage::CONTROLLER_PAGE_ANALOGS },
|
||||||
{ "Overlay", ControllerPage::CONTROLLER_PAGE_OVERLAY },
|
{ "Overlay", ControllerPage::CONTROLLER_PAGE_OVERLAY },
|
||||||
{ "Lights", ControllerPage::CONTROLLER_PAGE_LIGHTS },
|
{ "Lights", ControllerPage::CONTROLLER_PAGE_LIGHTS },
|
||||||
@@ -616,6 +617,21 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ControllerPage::CONTROLLER_PAGE_MODIFIERS: {
|
||||||
|
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Button Modifiers");
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"Bind modifier buttons below, then open Edit properties on any button binding "
|
||||||
|
"(except MIDI) to select the required modifiers. This allows you to configure "
|
||||||
|
"button combinations.\n\n"
|
||||||
|
"For example, if you bind the Start button as Modifier 1, and then bind "
|
||||||
|
"the A button for Service Coin and set it to require Modifier 1, you press "
|
||||||
|
"Start + A to trigger Service Coin.");
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
this->build_buttons(
|
||||||
|
"Modifier", games::get_buttons_modifiers(this->games_selected_name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ControllerPage::CONTROLLER_PAGE_ANALOGS: {
|
case ControllerPage::CONTROLLER_PAGE_ANALOGS: {
|
||||||
|
|
||||||
// help text for binding analog, if the game has one
|
// help text for binding analog, if the game has one
|
||||||
@@ -1256,7 +1272,8 @@ namespace overlay::windows {
|
|||||||
if (open_edit) {
|
if (open_edit) {
|
||||||
ImGui::OpenPopup(edit_name.c_str());
|
ImGui::OpenPopup(edit_name.c_str());
|
||||||
}
|
}
|
||||||
edit_button_popup(edit_name, button_display, button, button_velocity, alt_index);
|
edit_button_popup(
|
||||||
|
edit_name, button_display, button, button_velocity, alt_index, name != "Modifier");
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -1275,6 +1292,7 @@ namespace overlay::windows {
|
|||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
button->setInvert(false);
|
button->setInvert(false);
|
||||||
|
button->setModifierMask(0);
|
||||||
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
||||||
button->setLastVelocity(0);
|
button->setLastVelocity(0);
|
||||||
button->setTemporary(false);
|
button->setTemporary(false);
|
||||||
@@ -1370,6 +1388,7 @@ namespace overlay::windows {
|
|||||||
button->setDeviceIdentifier(device->name);
|
button->setDeviceIdentifier(device->name);
|
||||||
button->setVKey(static_cast<unsigned short>(i));
|
button->setVKey(static_cast<unsigned short>(i));
|
||||||
button->setAnalogType(BAT_NONE);
|
button->setAnalogType(BAT_NONE);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
@@ -1402,6 +1421,7 @@ namespace overlay::windows {
|
|||||||
button->setDeviceIdentifier(device->name);
|
button->setDeviceIdentifier(device->name);
|
||||||
button->setVKey(vkey);
|
button->setVKey(vkey);
|
||||||
button->setAnalogType(BAT_NONE);
|
button->setAnalogType(BAT_NONE);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
@@ -1537,6 +1557,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1582,6 +1603,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceUp(0.0);
|
button->setDebounceUp(0.0);
|
||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
// same idea as setMidiVKey - keep velocity threshold consistent
|
// same idea as setMidiVKey - keep velocity threshold consistent
|
||||||
button->setVelocityThreshold(
|
button->setVelocityThreshold(
|
||||||
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
||||||
@@ -1609,6 +1631,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1636,6 +1659,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1663,6 +1687,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1689,6 +1714,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1710,6 +1736,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1740,6 +1767,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1816,6 +1844,7 @@ namespace overlay::windows {
|
|||||||
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
|
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
|
||||||
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
|
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
|
||||||
button->setVKey(static_cast<uint16_t>(xinput.button));
|
button->setVKey(static_cast<uint16_t>(xinput.button));
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button, alt_index - 1);
|
games_list[games_selected], *button, alt_index - 1);
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
@@ -1872,6 +1901,7 @@ namespace overlay::windows {
|
|||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
button->setBatThreshold(0);
|
button->setBatThreshold(0);
|
||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
|
button->setModifierMask(0);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
games_list[games_selected], *button,
|
games_list[games_selected], *button,
|
||||||
alt_index - 1);
|
alt_index - 1);
|
||||||
@@ -1890,37 +1920,83 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Config::build_modifier_picker(Button &button) {
|
||||||
|
bool changed = false;
|
||||||
|
auto modifier_mask = button.getModifierMask();
|
||||||
|
|
||||||
|
// collect live states and build the closed dropdown summary
|
||||||
|
uint8_t pressed_modifier_mask = 0;
|
||||||
|
std::string modifier_preview;
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
const uint8_t modifier_bit = UINT8_C(1) << index;
|
||||||
|
if (modifier_buttons && index < modifier_buttons->size() &&
|
||||||
|
GameAPI::Buttons::getState(RI_MGR, modifier_buttons->at(index))) {
|
||||||
|
pressed_modifier_mask |= modifier_bit;
|
||||||
|
}
|
||||||
|
if ((modifier_mask & modifier_bit) != 0) {
|
||||||
|
modifier_preview += modifier_preview.empty() ? "Mod " : "+";
|
||||||
|
modifier_preview += std::to_string(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modifier_preview.empty()) {
|
||||||
|
modifier_preview = "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
// color the combo when all requirements are held, but keep its label unchanged
|
||||||
|
const bool all_modifiers_pressed = modifier_mask != 0 &&
|
||||||
|
(pressed_modifier_mask & modifier_mask) == modifier_mask;
|
||||||
|
if (all_modifiers_pressed) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
|
}
|
||||||
|
const bool combo_open = ImGui::BeginCombo("##Modifiers", modifier_preview.c_str());
|
||||||
|
if (all_modifiers_pressed) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update requirements while highlighting modifier sources that are currently pressed
|
||||||
|
if (combo_open) {
|
||||||
|
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||||
|
const uint8_t modifier_bit = UINT8_C(1) << index;
|
||||||
|
bool required = (modifier_mask & modifier_bit) != 0;
|
||||||
|
const auto label = fmt::format("Modifier {}", index + 1);
|
||||||
|
const bool modifier_pressed = (pressed_modifier_mask & modifier_bit) != 0;
|
||||||
|
if (modifier_pressed) {
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
|
}
|
||||||
|
const bool requirement_changed = ImGui::Checkbox(label.c_str(), &required);
|
||||||
|
if (modifier_pressed) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
if (requirement_changed) {
|
||||||
|
modifier_mask ^= modifier_bit;
|
||||||
|
button.setModifierMask(modifier_mask);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||||
|
ImGui::TextUnformatted("Modifiers");
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
void Config::edit_button_popup(
|
void Config::edit_button_popup(
|
||||||
const std::string &edit_name,
|
const std::string &edit_name,
|
||||||
const std::string &button_display,
|
const std::string &button_display,
|
||||||
Button *button,
|
Button *button,
|
||||||
const float button_velocity,
|
const float button_velocity,
|
||||||
const int alt_index) {
|
const int alt_index,
|
||||||
|
const bool allow_modifiers) {
|
||||||
|
|
||||||
const auto button_state = GameAPI::Buttons::getState(RI_MGR, *button, false);
|
const auto button_state = GameAPI::Buttons::getState(RI_MGR, *button, false);
|
||||||
if (ImGui::BeginPopupModal(edit_name.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
if (ImGui::BeginPopupModal(edit_name.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
auto device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
auto device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||||
|
|
||||||
// binding
|
auto switch_device = [&](const std::string &device_identifier) {
|
||||||
ImGui::Text("Binding");
|
button->setDeviceIdentifier(device_identifier);
|
||||||
|
|
||||||
// combo for devices
|
|
||||||
std::string device_desc = (device != nullptr) ? device->desc : "Empty (Naive)";
|
|
||||||
if (ImGui::BeginCombo("Device Identifier", device_desc.c_str())) {
|
|
||||||
if (ImGui::Selectable("Empty (Naive)", button->isNaive())) {
|
|
||||||
button->setDeviceIdentifier("");
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
if (button->isNaive()) {
|
|
||||||
ImGui::SetItemDefaultFocus();
|
|
||||||
}
|
|
||||||
for (auto &device : RI_MGR->devices_get()) {
|
|
||||||
bool selected = button->getDeviceIdentifier() == device.name.c_str();
|
|
||||||
const auto device_desc = fmt::format("{}##{}", device.desc, device.name);
|
|
||||||
if (ImGui::Selectable(device_desc.c_str(), selected)) {
|
|
||||||
button->setDeviceIdentifier(device.name);
|
|
||||||
// reset controls when switching devices
|
|
||||||
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
||||||
button->setDebounceUp(0.0);
|
button->setDebounceUp(0.0);
|
||||||
button->setDebounceDown(0.0);
|
button->setDebounceDown(0.0);
|
||||||
@@ -1928,7 +2004,28 @@ namespace overlay::windows {
|
|||||||
button->setVelocityThreshold(0);
|
button->setVelocityThreshold(0);
|
||||||
button->setVKey(0);
|
button->setVKey(0);
|
||||||
button->setInvert(false);
|
button->setInvert(false);
|
||||||
|
button->setModifierMask(0);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// binding
|
||||||
|
ImGui::Text("Binding");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// combo for devices
|
||||||
|
std::string device_desc = (device != nullptr) ? device->desc : "Empty (Naive)";
|
||||||
|
if (ImGui::BeginCombo("Device Identifier", device_desc.c_str())) {
|
||||||
|
if (ImGui::Selectable("Empty (Naive)", button->isNaive()) && !button->isNaive()) {
|
||||||
|
switch_device("");
|
||||||
|
}
|
||||||
|
if (button->isNaive()) {
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
for (auto &device : RI_MGR->devices_get()) {
|
||||||
|
bool selected = button->getDeviceIdentifier() == device.name.c_str();
|
||||||
|
const auto device_desc = fmt::format("{}##{}", device.desc, device.name);
|
||||||
|
if (ImGui::Selectable(device_desc.c_str(), selected) && !selected) {
|
||||||
|
switch_device(device.name);
|
||||||
}
|
}
|
||||||
if (selected) {
|
if (selected) {
|
||||||
ImGui::SetItemDefaultFocus();
|
ImGui::SetItemDefaultFocus();
|
||||||
@@ -1936,6 +2033,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
|
device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||||
|
|
||||||
// analog type (only for HID)
|
// analog type (only for HID)
|
||||||
const auto bat = button->getAnalogType();
|
const auto bat = button->getAnalogType();
|
||||||
@@ -2227,21 +2325,22 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifiers
|
||||||
|
{
|
||||||
|
const bool is_midi = device != nullptr && device->type == rawinput::MIDI;
|
||||||
|
if (allow_modifiers && button->isValid() && !is_midi) {
|
||||||
|
dirty |= build_modifier_picker(*button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
if (!button_display.empty()) {
|
if (!button_display.empty()) {
|
||||||
ImGui::TextUnformatted("\nPreview");
|
ImGui::TextUnformatted("\nPreview");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.7f, 0.f, 1.f));
|
ImGui::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||||
}
|
|
||||||
// this doesn't account for utf-8 but whatever
|
|
||||||
if (button_display.size() >= 40) {
|
|
||||||
ImGui::Text("%.37s...", button_display.c_str());
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::HelpMarker(button_display.c_str());
|
|
||||||
} else {
|
|
||||||
ImGui::TextUnformatted(button_display.c_str());
|
|
||||||
}
|
}
|
||||||
|
ImGui::TextTruncated(button_display, ImGui::GetContentRegionAvail().x);
|
||||||
ImGui::TextUnformatted("\n");
|
ImGui::TextUnformatted("\n");
|
||||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
@@ -2250,8 +2349,10 @@ namespace overlay::windows {
|
|||||||
ImGui::TextUnformatted("");
|
ImGui::TextUnformatted("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// options
|
// options
|
||||||
ImGui::Text("Options");
|
ImGui::TextUnformatted("Options");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
// check for debounce
|
// check for debounce
|
||||||
if (button->getDebounceUp() || button->getDebounceDown()
|
if (button->getDebounceUp() || button->getDebounceDown()
|
||||||
@@ -2370,6 +2471,7 @@ namespace overlay::windows {
|
|||||||
ImGui::ProgressBar(button_velocity);
|
ImGui::ProgressBar(button_velocity);
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("State");
|
ImGui::Text("State");
|
||||||
|
ImGui::Separator();
|
||||||
ImGui::ProgressBar(button_velocity);
|
ImGui::ProgressBar(button_velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5490,6 +5592,12 @@ namespace overlay::windows {
|
|||||||
tmpl.buttons.emplace_back(btn);
|
tmpl.buttons.emplace_back(btn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
if (modifier_buttons) {
|
||||||
|
for (auto &modifier : *modifier_buttons) {
|
||||||
|
tmpl.modifier_buttons.emplace_back(modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// capture keypad buttons
|
// capture keypad buttons
|
||||||
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||||
@@ -5545,6 +5653,17 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear all modifier buttons
|
||||||
|
auto *modifier_buttons = games::get_buttons_modifiers(this->games_selected_name);
|
||||||
|
if (modifier_buttons) {
|
||||||
|
for (auto &modifier : *modifier_buttons) {
|
||||||
|
for (int ai = (int)modifier.getAlternatives().size() - 1; ai >= 0; ai--) {
|
||||||
|
clear_button(&modifier.getAlternatives()[ai], ai + 1);
|
||||||
|
}
|
||||||
|
clear_button(&modifier, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clear all keypad buttons
|
// clear all keypad buttons
|
||||||
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||||
if (keypad_buttons) {
|
if (keypad_buttons) {
|
||||||
@@ -5638,6 +5757,7 @@ namespace overlay::windows {
|
|||||||
btn.setDebounceDown(entry->debounce_down);
|
btn.setDebounceDown(entry->debounce_down);
|
||||||
btn.setBatThreshold(entry->bat_threshold);
|
btn.setBatThreshold(entry->bat_threshold);
|
||||||
btn.setVelocityThreshold(entry->velocity_threshold);
|
btn.setVelocityThreshold(entry->velocity_threshold);
|
||||||
|
btn.setModifierMask(entry->modifier_mask);
|
||||||
::Config::getInstance().updateBinding(game, btn, -1);
|
::Config::getInstance().updateBinding(game, btn, -1);
|
||||||
} else {
|
} else {
|
||||||
Button alt_btn(btn.getName());
|
Button alt_btn(btn.getName());
|
||||||
@@ -5649,6 +5769,7 @@ namespace overlay::windows {
|
|||||||
alt_btn.setDebounceDown(entry->debounce_down);
|
alt_btn.setDebounceDown(entry->debounce_down);
|
||||||
alt_btn.setBatThreshold(entry->bat_threshold);
|
alt_btn.setBatThreshold(entry->bat_threshold);
|
||||||
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
||||||
|
alt_btn.setModifierMask(entry->modifier_mask);
|
||||||
alt_btn.setTemporary(true);
|
alt_btn.setTemporary(true);
|
||||||
btn.getAlternatives().push_back(alt_btn);
|
btn.getAlternatives().push_back(alt_btn);
|
||||||
::Config::getInstance().updateBinding(
|
::Config::getInstance().updateBinding(
|
||||||
@@ -5667,6 +5788,11 @@ namespace overlay::windows {
|
|||||||
if (this->apply_buttons) {
|
if (this->apply_buttons) {
|
||||||
apply_buttons(tmpl.buttons, games::get_buttons(this->games_selected_name));
|
apply_buttons(tmpl.buttons, games::get_buttons(this->games_selected_name));
|
||||||
}
|
}
|
||||||
|
if (this->apply_modifiers) {
|
||||||
|
apply_buttons(
|
||||||
|
tmpl.modifier_buttons,
|
||||||
|
games::get_buttons_modifiers(this->games_selected_name));
|
||||||
|
}
|
||||||
if (this->apply_keypads) {
|
if (this->apply_keypads) {
|
||||||
apply_buttons(tmpl.keypad_buttons, games::get_buttons_keypads(this->games_selected_name));
|
apply_buttons(tmpl.keypad_buttons, games::get_buttons_keypads(this->games_selected_name));
|
||||||
}
|
}
|
||||||
@@ -5908,7 +6034,7 @@ namespace overlay::windows {
|
|||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::HelpMarker(
|
ImGui::HelpMarker(
|
||||||
"Clears all game button, analog, and light bindings.");
|
"Clears all game button, modifier, keypad, analog, and light bindings.");
|
||||||
if (this->all_cleared) {
|
if (this->all_cleared) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::TextUnformatted("Done.");
|
ImGui::TextUnformatted("Done.");
|
||||||
@@ -5923,6 +6049,8 @@ namespace overlay::windows {
|
|||||||
bool selection_changed = false;
|
bool selection_changed = false;
|
||||||
selection_changed |= ImGui::Checkbox("Buttons", &this->apply_buttons);
|
selection_changed |= ImGui::Checkbox("Buttons", &this->apply_buttons);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
selection_changed |= ImGui::Checkbox("Modifiers", &this->apply_modifiers);
|
||||||
|
ImGui::SameLine();
|
||||||
selection_changed |= ImGui::Checkbox("Keypads", &this->apply_keypads);
|
selection_changed |= ImGui::Checkbox("Keypads", &this->apply_keypads);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
selection_changed |= ImGui::Checkbox("Analogs", &this->apply_analogs);
|
selection_changed |= ImGui::Checkbox("Analogs", &this->apply_analogs);
|
||||||
@@ -5932,7 +6060,7 @@ namespace overlay::windows {
|
|||||||
std::fill(this->template_is_applied.begin(), this->template_is_applied.end(), false);
|
std::fill(this->template_is_applied.begin(), this->template_is_applied.end(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->apply_buttons && !this->apply_keypads &&
|
if (!this->apply_buttons && !this->apply_modifiers && !this->apply_keypads &&
|
||||||
!this->apply_analogs && !this->apply_lights) {
|
!this->apply_analogs && !this->apply_lights) {
|
||||||
ImGui::TextUnformatted("\nYou must select at least one group to apply.\n\n");
|
ImGui::TextUnformatted("\nYou must select at least one group to apply.\n\n");
|
||||||
}
|
}
|
||||||
@@ -5972,7 +6100,8 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
if (!apply_buttons && !apply_modifiers && !apply_keypads &&
|
||||||
|
!apply_analogs && !apply_lights) {
|
||||||
ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6141,7 +6270,8 @@ namespace overlay::windows {
|
|||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
if (!apply_buttons && !apply_modifiers && !apply_keypads &&
|
||||||
|
!apply_analogs && !apply_lights) {
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6220,12 +6350,14 @@ namespace overlay::windows {
|
|||||||
ImGui::TextUnformatted("Pick which groups to save:");
|
ImGui::TextUnformatted("Pick which groups to save:");
|
||||||
ImGui::Checkbox("Buttons", &this->save_buttons);
|
ImGui::Checkbox("Buttons", &this->save_buttons);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Modifiers", &this->save_modifiers);
|
||||||
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Keypads", &this->save_keypads);
|
ImGui::Checkbox("Keypads", &this->save_keypads);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Analogs", &this->save_analogs);
|
ImGui::Checkbox("Analogs", &this->save_analogs);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Checkbox("Lights", &this->save_lights);
|
ImGui::Checkbox("Lights", &this->save_lights);
|
||||||
if (!this->save_analogs && !this->save_keypads &&
|
if (!this->save_buttons && !this->save_modifiers && !this->save_keypads &&
|
||||||
!this->save_analogs && !this->save_lights) {
|
!this->save_analogs && !this->save_lights) {
|
||||||
ImGui::TextUnformatted("\nYou must select at least one group to save.\n\n");
|
ImGui::TextUnformatted("\nYou must select at least one group to save.\n\n");
|
||||||
}
|
}
|
||||||
@@ -6277,7 +6409,8 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
ImGui::BeginDisabled(
|
ImGui::BeginDisabled(
|
||||||
!all_labels_set ||
|
!all_labels_set ||
|
||||||
(!this->save_buttons && !this->save_keypads && !this->save_analogs && !this->save_lights));
|
(!this->save_buttons && !this->save_modifiers && !this->save_keypads &&
|
||||||
|
!this->save_analogs && !this->save_lights));
|
||||||
|
|
||||||
if (ImGui::Button("Save")) {
|
if (ImGui::Button("Save")) {
|
||||||
// replace device IDs with labels in the template
|
// replace device IDs with labels in the template
|
||||||
@@ -6286,7 +6419,8 @@ namespace overlay::windows {
|
|||||||
template_save_sources[si], template_save_labels[si]);
|
template_save_sources[si], template_save_labels[si]);
|
||||||
}
|
}
|
||||||
if (save_user_template(template_pending_save,
|
if (save_user_template(template_pending_save,
|
||||||
this->save_buttons, this->save_keypads, this->save_analogs, this->save_lights)) {
|
this->save_buttons, this->save_modifiers, this->save_keypads,
|
||||||
|
this->save_analogs, this->save_lights)) {
|
||||||
template_save_name[0] = '\0';
|
template_save_name[0] = '\0';
|
||||||
templates_cache_dirty = true;
|
templates_cache_dirty = true;
|
||||||
}
|
}
|
||||||
@@ -6368,7 +6502,7 @@ namespace overlay::windows {
|
|||||||
t.rename_source(template_save_sources[si], template_save_labels[si]);
|
t.rename_source(template_save_sources[si], template_save_labels[si]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
save_user_template(t, true, true, true, true);
|
save_user_template(t, true, true, true, true, true);
|
||||||
templates_cache_dirty = true;
|
templates_cache_dirty = true;
|
||||||
template_save_sources.clear();
|
template_save_sources.clear();
|
||||||
template_save_labels.clear();
|
template_save_labels.clear();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
|||||||
CONTROLLER_PAGE_INVALID,
|
CONTROLLER_PAGE_INVALID,
|
||||||
CONTROLLER_PAGE_BUTTONS,
|
CONTROLLER_PAGE_BUTTONS,
|
||||||
CONTROLLER_PAGE_KEYPADS,
|
CONTROLLER_PAGE_KEYPADS,
|
||||||
|
CONTROLLER_PAGE_MODIFIERS,
|
||||||
CONTROLLER_PAGE_ANALOGS,
|
CONTROLLER_PAGE_ANALOGS,
|
||||||
CONTROLLER_PAGE_OVERLAY,
|
CONTROLLER_PAGE_OVERLAY,
|
||||||
CONTROLLER_PAGE_LIGHTS,
|
CONTROLLER_PAGE_LIGHTS,
|
||||||
@@ -119,10 +120,12 @@ namespace overlay::windows {
|
|||||||
std::vector<std::string> template_save_sources;
|
std::vector<std::string> template_save_sources;
|
||||||
std::vector<std::string> template_save_labels;
|
std::vector<std::string> template_save_labels;
|
||||||
bool apply_buttons = true;
|
bool apply_buttons = true;
|
||||||
|
bool apply_modifiers = true;
|
||||||
bool apply_keypads = true;
|
bool apply_keypads = true;
|
||||||
bool apply_analogs = true;
|
bool apply_analogs = true;
|
||||||
bool apply_lights = true;
|
bool apply_lights = true;
|
||||||
bool save_buttons = true;
|
bool save_buttons = true;
|
||||||
|
bool save_modifiers = true;
|
||||||
bool save_keypads = true;
|
bool save_keypads = true;
|
||||||
bool save_analogs = true;
|
bool save_analogs = true;
|
||||||
bool save_lights = true;
|
bool save_lights = true;
|
||||||
@@ -168,12 +171,14 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
|
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
|
||||||
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
|
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
|
||||||
|
bool build_modifier_picker(Button &button);
|
||||||
void edit_button_popup(
|
void edit_button_popup(
|
||||||
const std::string &edit_name,
|
const std::string &edit_name,
|
||||||
const std::string &button_display,
|
const std::string &button_display,
|
||||||
Button *button,
|
Button *button,
|
||||||
const float button_velocity,
|
const float button_velocity,
|
||||||
const int alt_index);
|
const int alt_index,
|
||||||
|
const bool allow_modifiers);
|
||||||
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
|
void clear_button(Button *button, const int alt_index, std::optional<unsigned short> vKey_default = std::nullopt);
|
||||||
void reset_button_to_default(Button *button, unsigned short vKey_default);
|
void reset_button_to_default(Button *button, unsigned short vKey_default);
|
||||||
unsigned int get_keypad_top_row(const Button &button);
|
unsigned int get_keypad_top_row(const Button &button);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
// helpers - iterate both buttons and keypad_buttons
|
// helpers - iterate all button groups
|
||||||
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
|
static void count_buttons_in(const std::vector<TemplateButtonBinding> &btns, int &naive, int &device) {
|
||||||
for (auto &btn : btns) {
|
for (auto &btn : btns) {
|
||||||
if (btn.primary.is_naive()) naive++;
|
if (btn.primary.is_naive()) naive++;
|
||||||
@@ -21,6 +21,7 @@ namespace overlay::windows {
|
|||||||
int ControllerTemplate::count_naive_buttons() const {
|
int ControllerTemplate::count_naive_buttons() const {
|
||||||
int naive = 0, device = 0;
|
int naive = 0, device = 0;
|
||||||
count_buttons_in(buttons, naive, device);
|
count_buttons_in(buttons, naive, device);
|
||||||
|
count_buttons_in(modifier_buttons, naive, device);
|
||||||
count_buttons_in(keypad_buttons, naive, device);
|
count_buttons_in(keypad_buttons, naive, device);
|
||||||
return naive;
|
return naive;
|
||||||
}
|
}
|
||||||
@@ -28,6 +29,7 @@ namespace overlay::windows {
|
|||||||
int ControllerTemplate::count_device_buttons() const {
|
int ControllerTemplate::count_device_buttons() const {
|
||||||
int naive = 0, device = 0;
|
int naive = 0, device = 0;
|
||||||
count_buttons_in(buttons, naive, device);
|
count_buttons_in(buttons, naive, device);
|
||||||
|
count_buttons_in(modifier_buttons, naive, device);
|
||||||
count_buttons_in(keypad_buttons, naive, device);
|
count_buttons_in(keypad_buttons, naive, device);
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
@@ -64,6 +66,7 @@ namespace overlay::windows {
|
|||||||
std::set<std::string> ControllerTemplate::get_used_devices() const {
|
std::set<std::string> ControllerTemplate::get_used_devices() const {
|
||||||
std::set<std::string> devices;
|
std::set<std::string> devices;
|
||||||
collect_devices_from(buttons, devices);
|
collect_devices_from(buttons, devices);
|
||||||
|
collect_devices_from(modifier_buttons, devices);
|
||||||
collect_devices_from(keypad_buttons, devices);
|
collect_devices_from(keypad_buttons, devices);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.is_device()) devices.insert(a.device_identifier);
|
if (a.is_device()) devices.insert(a.device_identifier);
|
||||||
@@ -94,6 +97,7 @@ namespace overlay::windows {
|
|||||||
bool has_naive = false;
|
bool has_naive = false;
|
||||||
|
|
||||||
collect_sources_from(buttons, sources_set, has_naive);
|
collect_sources_from(buttons, sources_set, has_naive);
|
||||||
|
collect_sources_from(modifier_buttons, sources_set, has_naive);
|
||||||
collect_sources_from(keypad_buttons, sources_set, has_naive);
|
collect_sources_from(keypad_buttons, sources_set, has_naive);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.is_device()) {
|
if (a.is_device()) {
|
||||||
@@ -131,6 +135,7 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
rename_in_buttons(buttons);
|
rename_in_buttons(buttons);
|
||||||
|
rename_in_buttons(modifier_buttons);
|
||||||
rename_in_buttons(keypad_buttons);
|
rename_in_buttons(keypad_buttons);
|
||||||
for (auto &a : analogs) {
|
for (auto &a : analogs) {
|
||||||
if (a.device_identifier == old_id) a.device_identifier = new_id;
|
if (a.device_identifier == old_id) a.device_identifier = new_id;
|
||||||
@@ -181,6 +186,16 @@ namespace overlay::windows {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modifier buttons
|
||||||
|
std::vector<std::string> modifier_lines;
|
||||||
|
collect_btn_lines(modifier_buttons, modifier_lines);
|
||||||
|
if (!modifier_lines.empty()) {
|
||||||
|
result += "Modifiers:\n";
|
||||||
|
for (auto &line : modifier_lines) {
|
||||||
|
result += " " + line + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// keypad buttons
|
// keypad buttons
|
||||||
std::vector<std::string> kp_lines;
|
std::vector<std::string> kp_lines;
|
||||||
collect_btn_lines(keypad_buttons, kp_lines);
|
collect_btn_lines(keypad_buttons, kp_lines);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace overlay::windows {
|
|||||||
double debounce_down = 0.0;
|
double debounce_down = 0.0;
|
||||||
int bat_threshold = 0;
|
int bat_threshold = 0;
|
||||||
unsigned short velocity_threshold = 0;
|
unsigned short velocity_threshold = 0;
|
||||||
|
uint8_t modifier_mask = 0;
|
||||||
|
|
||||||
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
|
bool is_naive() const { return device_identifier.empty() && vKey != INVALID_VKEY; }
|
||||||
bool is_device() const { return !device_identifier.empty(); }
|
bool is_device() const { return !device_identifier.empty(); }
|
||||||
@@ -34,6 +35,7 @@ namespace overlay::windows {
|
|||||||
e.debounce_down = btn.getDebounceDown();
|
e.debounce_down = btn.getDebounceDown();
|
||||||
e.bat_threshold = btn.getBatThreshold();
|
e.bat_threshold = btn.getBatThreshold();
|
||||||
e.velocity_threshold = btn.getVelocityThreshold();
|
e.velocity_threshold = btn.getVelocityThreshold();
|
||||||
|
e.modifier_mask = btn.getModifierMask();
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -123,6 +125,7 @@ namespace overlay::windows {
|
|||||||
std::string game_name;
|
std::string game_name;
|
||||||
bool is_builtin = false;
|
bool is_builtin = false;
|
||||||
std::vector<TemplateButtonBinding> buttons;
|
std::vector<TemplateButtonBinding> buttons;
|
||||||
|
std::vector<TemplateButtonBinding> modifier_buttons;
|
||||||
std::vector<TemplateButtonBinding> keypad_buttons;
|
std::vector<TemplateButtonBinding> keypad_buttons;
|
||||||
std::vector<TemplateAnalogBinding> analogs;
|
std::vector<TemplateAnalogBinding> analogs;
|
||||||
std::vector<TemplateLightBinding> lights;
|
std::vector<TemplateLightBinding> lights;
|
||||||
@@ -150,6 +153,7 @@ namespace overlay::windows {
|
|||||||
bool save_user_template(
|
bool save_user_template(
|
||||||
const ControllerTemplate &tmpl,
|
const ControllerTemplate &tmpl,
|
||||||
const bool save_buttons,
|
const bool save_buttons,
|
||||||
|
const bool save_modifiers,
|
||||||
const bool save_keypads,
|
const bool save_keypads,
|
||||||
const bool save_analogs,
|
const bool save_analogs,
|
||||||
const bool save_lights);
|
const bool save_lights);
|
||||||
|
|||||||
Reference in New Issue
Block a user