mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20: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 <optional>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "launcher/superexit.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "rawinput/piuio.h"
|
||||
#include "util/time.h"
|
||||
@@ -62,7 +64,43 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
||||
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
|
||||
if (_button.override_enabled) {
|
||||
@@ -76,6 +114,23 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
std::optional<bool> window_has_focus;
|
||||
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
|
||||
if (current_button->isNaive()) {
|
||||
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) {
|
||||
if (manager) {
|
||||
return getState(manager.get(), button, check_alts);
|
||||
@@ -484,17 +543,13 @@ static float getVelocityHelper(rawinput::RawInputManager *manager, Button &butto
|
||||
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
|
||||
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
|
||||
if (button_state != Buttons::BUTTON_PRESSED) {
|
||||
|
||||
@@ -300,6 +300,26 @@ std::string Button::getMidiNoteString() {
|
||||
}
|
||||
|
||||
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
|
||||
auto vKey = (uint16_t) this->getVKey();
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
std::string name;
|
||||
std::string device_identifier = "";
|
||||
unsigned short vKey = INVALID_VKEY;
|
||||
uint8_t modifier_mask = 0;
|
||||
|
||||
// default bindings are always naive
|
||||
unsigned short vKey_default = INVALID_VKEY;
|
||||
@@ -65,6 +66,7 @@ private:
|
||||
unsigned short velocity_threshold = 0;
|
||||
|
||||
std::string getMidiNoteString();
|
||||
std::string getBindingDisplayString(rawinput::RawInputManager *manager);
|
||||
|
||||
public:
|
||||
std::string getVKeyString();
|
||||
@@ -111,6 +113,7 @@ public:
|
||||
device_identifier = "";
|
||||
analog_type = BAT_NONE;
|
||||
bat_threshold = 0;
|
||||
modifier_mask = 0;
|
||||
}
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -123,6 +126,14 @@ public:
|
||||
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 {
|
||||
return this->device_identifier;
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
unsigned int modifier_mask = 0;
|
||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
@@ -173,6 +174,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
gameButtonsNode->DeleteChild(gameButtonNode);
|
||||
gameButtonNode = this->configFile.NewElement("button");
|
||||
@@ -185,6 +187,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||
gameButtonNode->SetAttribute("invert", invert);
|
||||
gameButtonNode->SetAttribute("modifiers", button->getModifierMask());
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
} else {
|
||||
button->setVKey(static_cast<unsigned short int>(vKey));
|
||||
@@ -194,6 +197,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
button->setBatThreshold(bat_threshold);
|
||||
button->setVelocityThreshold(velocity_threshold);
|
||||
button->setInvert(invert);
|
||||
button->setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
button->setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -214,6 +218,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
}
|
||||
@@ -440,6 +445,7 @@ bool Config::addGame(Game &game, bool save) {
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
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("invert", button.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||
gameButtonNode->SetAttribute("modifiers", button.getModifierMask());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -569,6 +576,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||
gameButtonNode->SetAttribute("invert", false);
|
||||
gameButtonNode->SetAttribute("devid", "");
|
||||
gameButtonNode->SetAttribute("modifiers", 0);
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
}
|
||||
}
|
||||
@@ -964,6 +972,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
unsigned int modifier_mask = 0;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
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("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
|
||||
// find alternative
|
||||
@@ -986,6 +996,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
alt.setBatThreshold(bat_threshold);
|
||||
alt.setVelocityThreshold(velocity_threshold);
|
||||
alt.setInvert(invert);
|
||||
alt.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
alt.setDeviceIdentifier(std::string(devid));
|
||||
}
|
||||
@@ -1005,6 +1016,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
button.setBatThreshold(bat_threshold);
|
||||
button.setVelocityThreshold(velocity_threshold);
|
||||
button.setInvert(invert);
|
||||
button.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
button.setDeviceIdentifier(devid);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||
el->SetAttribute("modifiers", entry.modifier_mask);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
@@ -54,6 +55,10 @@ namespace overlay::windows {
|
||||
el->QueryIntAttribute("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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
||||
if (kp_el) {
|
||||
@@ -244,6 +271,7 @@ namespace overlay::windows {
|
||||
bool save_user_template(
|
||||
const ControllerTemplate &tmpl,
|
||||
const bool save_buttons,
|
||||
const bool save_modifiers,
|
||||
const bool save_keypads,
|
||||
const bool save_analogs,
|
||||
const bool save_lights) {
|
||||
@@ -300,6 +328,24 @@ namespace overlay::windows {
|
||||
}
|
||||
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
|
||||
if (!tmpl.keypad_buttons.empty()) {
|
||||
auto *kp_el = doc.NewElement("keypad_buttons");
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace games {
|
||||
static bool IO_INITIALIZED = false;
|
||||
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_modifiers;
|
||||
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::string> buttons_help;
|
||||
@@ -365,6 +366,37 @@ namespace games {
|
||||
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) {
|
||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||
std::vector<std::string> names;
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
|
||||
namespace games {
|
||||
|
||||
namespace ModifierButtons {
|
||||
enum {
|
||||
Modifier1,
|
||||
Modifier2,
|
||||
Modifier3,
|
||||
Modifier4,
|
||||
Size,
|
||||
};
|
||||
}
|
||||
|
||||
namespace OverlayButtons {
|
||||
enum {
|
||||
Screenshot,
|
||||
@@ -59,6 +69,7 @@ namespace games {
|
||||
|
||||
const std::vector<std::string> &get_games();
|
||||
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_analogs_help(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 = {
|
||||
{ "Buttons", ControllerPage::CONTROLLER_PAGE_BUTTONS },
|
||||
{ "Keypads", ControllerPage::CONTROLLER_PAGE_KEYPADS },
|
||||
{ "Modifiers", ControllerPage::CONTROLLER_PAGE_MODIFIERS },
|
||||
{ "Analogs", ControllerPage::CONTROLLER_PAGE_ANALOGS },
|
||||
{ "Overlay", ControllerPage::CONTROLLER_PAGE_OVERLAY },
|
||||
{ "Lights", ControllerPage::CONTROLLER_PAGE_LIGHTS },
|
||||
@@ -616,6 +617,21 @@ namespace overlay::windows {
|
||||
}
|
||||
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: {
|
||||
|
||||
// help text for binding analog, if the game has one
|
||||
@@ -1256,7 +1272,8 @@ namespace overlay::windows {
|
||||
if (open_edit) {
|
||||
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
|
||||
ImGui::PopID();
|
||||
@@ -1275,6 +1292,7 @@ namespace overlay::windows {
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setInvert(false);
|
||||
button->setModifierMask(0);
|
||||
button->setLastState(GameAPI::Buttons::BUTTON_NOT_PRESSED);
|
||||
button->setLastVelocity(0);
|
||||
button->setTemporary(false);
|
||||
@@ -1370,6 +1388,7 @@ namespace overlay::windows {
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(i));
|
||||
button->setAnalogType(BAT_NONE);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button, alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
@@ -1402,6 +1421,7 @@ namespace overlay::windows {
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(vkey);
|
||||
button->setAnalogType(BAT_NONE);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button, alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
@@ -1537,6 +1557,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1582,6 +1603,7 @@ namespace overlay::windows {
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
// same idea as setMidiVKey - keep velocity threshold consistent
|
||||
button->setVelocityThreshold(
|
||||
device->midiInfo->v2_velocity_threshold[button->getVKey()]);
|
||||
@@ -1609,6 +1631,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1636,6 +1659,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1663,6 +1687,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1689,6 +1714,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1710,6 +1736,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1740,6 +1767,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
@@ -1816,6 +1844,7 @@ namespace overlay::windows {
|
||||
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
|
||||
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
|
||||
button->setVKey(static_cast<uint16_t>(xinput.button));
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button, alt_index - 1);
|
||||
inc_buttons_many_index(button_it_max);
|
||||
@@ -1872,6 +1901,7 @@ namespace overlay::windows {
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
button->setModifierMask(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
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(
|
||||
const std::string &edit_name,
|
||||
const std::string &button_display,
|
||||
Button *button,
|
||||
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);
|
||||
if (ImGui::BeginPopupModal(edit_name.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
bool dirty = false;
|
||||
auto device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||
|
||||
// binding
|
||||
ImGui::Text("Binding");
|
||||
|
||||
// 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
|
||||
auto switch_device = [&](const std::string &device_identifier) {
|
||||
button->setDeviceIdentifier(device_identifier);
|
||||
button->setAnalogType(ButtonAnalogType::BAT_NONE);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
@@ -1928,7 +2004,28 @@ namespace overlay::windows {
|
||||
button->setVelocityThreshold(0);
|
||||
button->setVKey(0);
|
||||
button->setInvert(false);
|
||||
button->setModifierMask(0);
|
||||
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) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
@@ -1936,6 +2033,7 @@ namespace overlay::windows {
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
device = RI_MGR->devices_get(button->getDeviceIdentifier());
|
||||
|
||||
// analog type (only for HID)
|
||||
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
|
||||
if (!button_display.empty()) {
|
||||
ImGui::TextUnformatted("\nPreview");
|
||||
ImGui::Separator();
|
||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.7f, 0.f, 1.f));
|
||||
}
|
||||
// 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::PushStyleColor(ImGuiCol_Text, TEXT_COLOR_GREEN);
|
||||
}
|
||||
ImGui::TextTruncated(button_display, ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TextUnformatted("\n");
|
||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||
ImGui::PopStyleColor();
|
||||
@@ -2250,8 +2349,10 @@ namespace overlay::windows {
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
|
||||
|
||||
// options
|
||||
ImGui::Text("Options");
|
||||
ImGui::TextUnformatted("Options");
|
||||
ImGui::Separator();
|
||||
|
||||
// check for debounce
|
||||
if (button->getDebounceUp() || button->getDebounceDown()
|
||||
@@ -2370,6 +2471,7 @@ namespace overlay::windows {
|
||||
ImGui::ProgressBar(button_velocity);
|
||||
} else {
|
||||
ImGui::Text("State");
|
||||
ImGui::Separator();
|
||||
ImGui::ProgressBar(button_velocity);
|
||||
}
|
||||
|
||||
@@ -5490,6 +5592,12 @@ namespace overlay::windows {
|
||||
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
|
||||
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
|
||||
auto *keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||
if (keypad_buttons) {
|
||||
@@ -5638,6 +5757,7 @@ namespace overlay::windows {
|
||||
btn.setDebounceDown(entry->debounce_down);
|
||||
btn.setBatThreshold(entry->bat_threshold);
|
||||
btn.setVelocityThreshold(entry->velocity_threshold);
|
||||
btn.setModifierMask(entry->modifier_mask);
|
||||
::Config::getInstance().updateBinding(game, btn, -1);
|
||||
} else {
|
||||
Button alt_btn(btn.getName());
|
||||
@@ -5649,6 +5769,7 @@ namespace overlay::windows {
|
||||
alt_btn.setDebounceDown(entry->debounce_down);
|
||||
alt_btn.setBatThreshold(entry->bat_threshold);
|
||||
alt_btn.setVelocityThreshold(entry->velocity_threshold);
|
||||
alt_btn.setModifierMask(entry->modifier_mask);
|
||||
alt_btn.setTemporary(true);
|
||||
btn.getAlternatives().push_back(alt_btn);
|
||||
::Config::getInstance().updateBinding(
|
||||
@@ -5667,6 +5788,11 @@ namespace overlay::windows {
|
||||
if (this->apply_buttons) {
|
||||
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) {
|
||||
apply_buttons(tmpl.keypad_buttons, games::get_buttons_keypads(this->games_selected_name));
|
||||
}
|
||||
@@ -5908,7 +6034,7 @@ namespace overlay::windows {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(
|
||||
"Clears all game button, analog, and light bindings.");
|
||||
"Clears all game button, modifier, keypad, analog, and light bindings.");
|
||||
if (this->all_cleared) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Done.");
|
||||
@@ -5923,6 +6049,8 @@ namespace overlay::windows {
|
||||
bool selection_changed = false;
|
||||
selection_changed |= ImGui::Checkbox("Buttons", &this->apply_buttons);
|
||||
ImGui::SameLine();
|
||||
selection_changed |= ImGui::Checkbox("Modifiers", &this->apply_modifiers);
|
||||
ImGui::SameLine();
|
||||
selection_changed |= ImGui::Checkbox("Keypads", &this->apply_keypads);
|
||||
ImGui::SameLine();
|
||||
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);
|
||||
}
|
||||
|
||||
if (!this->apply_buttons && !this->apply_keypads &&
|
||||
if (!this->apply_buttons && !this->apply_modifiers && !this->apply_keypads &&
|
||||
!this->apply_analogs && !this->apply_lights) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -6141,7 +6270,8 @@ namespace overlay::windows {
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
||||
if (!apply_buttons && !apply_modifiers && !apply_keypads &&
|
||||
!apply_analogs && !apply_lights) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
@@ -6220,12 +6350,14 @@ namespace overlay::windows {
|
||||
ImGui::TextUnformatted("Pick which groups to save:");
|
||||
ImGui::Checkbox("Buttons", &this->save_buttons);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Modifiers", &this->save_modifiers);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Keypads", &this->save_keypads);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Analogs", &this->save_analogs);
|
||||
ImGui::SameLine();
|
||||
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) {
|
||||
ImGui::TextUnformatted("\nYou must select at least one group to save.\n\n");
|
||||
}
|
||||
@@ -6277,7 +6409,8 @@ namespace overlay::windows {
|
||||
|
||||
ImGui::BeginDisabled(
|
||||
!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")) {
|
||||
// replace device IDs with labels in the template
|
||||
@@ -6286,7 +6419,8 @@ namespace overlay::windows {
|
||||
template_save_sources[si], template_save_labels[si]);
|
||||
}
|
||||
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';
|
||||
templates_cache_dirty = true;
|
||||
}
|
||||
@@ -6368,7 +6502,7 @@ namespace overlay::windows {
|
||||
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;
|
||||
template_save_sources.clear();
|
||||
template_save_labels.clear();
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace overlay::windows {
|
||||
CONTROLLER_PAGE_INVALID,
|
||||
CONTROLLER_PAGE_BUTTONS,
|
||||
CONTROLLER_PAGE_KEYPADS,
|
||||
CONTROLLER_PAGE_MODIFIERS,
|
||||
CONTROLLER_PAGE_ANALOGS,
|
||||
CONTROLLER_PAGE_OVERLAY,
|
||||
CONTROLLER_PAGE_LIGHTS,
|
||||
@@ -119,10 +120,12 @@ namespace overlay::windows {
|
||||
std::vector<std::string> template_save_sources;
|
||||
std::vector<std::string> template_save_labels;
|
||||
bool apply_buttons = true;
|
||||
bool apply_modifiers = true;
|
||||
bool apply_keypads = true;
|
||||
bool apply_analogs = true;
|
||||
bool apply_lights = true;
|
||||
bool save_buttons = true;
|
||||
bool save_modifiers = true;
|
||||
bool save_keypads = true;
|
||||
bool save_analogs = 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 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(
|
||||
const std::string &edit_name,
|
||||
const std::string &button_display,
|
||||
Button *button,
|
||||
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 reset_button_to_default(Button *button, unsigned short vKey_default);
|
||||
unsigned int get_keypad_top_row(const Button &button);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
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) {
|
||||
for (auto &btn : btns) {
|
||||
if (btn.primary.is_naive()) naive++;
|
||||
@@ -21,6 +21,7 @@ namespace overlay::windows {
|
||||
int ControllerTemplate::count_naive_buttons() const {
|
||||
int naive = 0, device = 0;
|
||||
count_buttons_in(buttons, naive, device);
|
||||
count_buttons_in(modifier_buttons, naive, device);
|
||||
count_buttons_in(keypad_buttons, naive, device);
|
||||
return naive;
|
||||
}
|
||||
@@ -28,6 +29,7 @@ namespace overlay::windows {
|
||||
int ControllerTemplate::count_device_buttons() const {
|
||||
int naive = 0, device = 0;
|
||||
count_buttons_in(buttons, naive, device);
|
||||
count_buttons_in(modifier_buttons, naive, device);
|
||||
count_buttons_in(keypad_buttons, naive, device);
|
||||
return device;
|
||||
}
|
||||
@@ -64,6 +66,7 @@ namespace overlay::windows {
|
||||
std::set<std::string> ControllerTemplate::get_used_devices() const {
|
||||
std::set<std::string> devices;
|
||||
collect_devices_from(buttons, devices);
|
||||
collect_devices_from(modifier_buttons, devices);
|
||||
collect_devices_from(keypad_buttons, devices);
|
||||
for (auto &a : analogs) {
|
||||
if (a.is_device()) devices.insert(a.device_identifier);
|
||||
@@ -94,6 +97,7 @@ namespace overlay::windows {
|
||||
bool has_naive = false;
|
||||
|
||||
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);
|
||||
for (auto &a : analogs) {
|
||||
if (a.is_device()) {
|
||||
@@ -131,6 +135,7 @@ namespace overlay::windows {
|
||||
}
|
||||
};
|
||||
rename_in_buttons(buttons);
|
||||
rename_in_buttons(modifier_buttons);
|
||||
rename_in_buttons(keypad_buttons);
|
||||
for (auto &a : analogs) {
|
||||
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
|
||||
std::vector<std::string> kp_lines;
|
||||
collect_btn_lines(keypad_buttons, kp_lines);
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace overlay::windows {
|
||||
double debounce_down = 0.0;
|
||||
int bat_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_device() const { return !device_identifier.empty(); }
|
||||
@@ -34,6 +35,7 @@ namespace overlay::windows {
|
||||
e.debounce_down = btn.getDebounceDown();
|
||||
e.bat_threshold = btn.getBatThreshold();
|
||||
e.velocity_threshold = btn.getVelocityThreshold();
|
||||
e.modifier_mask = btn.getModifierMask();
|
||||
return e;
|
||||
}
|
||||
};
|
||||
@@ -123,6 +125,7 @@ namespace overlay::windows {
|
||||
std::string game_name;
|
||||
bool is_builtin = false;
|
||||
std::vector<TemplateButtonBinding> buttons;
|
||||
std::vector<TemplateButtonBinding> modifier_buttons;
|
||||
std::vector<TemplateButtonBinding> keypad_buttons;
|
||||
std::vector<TemplateAnalogBinding> analogs;
|
||||
std::vector<TemplateLightBinding> lights;
|
||||
@@ -150,6 +153,7 @@ namespace overlay::windows {
|
||||
bool save_user_template(
|
||||
const ControllerTemplate &tmpl,
|
||||
const bool save_buttons,
|
||||
const bool save_modifiers,
|
||||
const bool save_keypads,
|
||||
const bool save_analogs,
|
||||
const bool save_lights);
|
||||
|
||||
Reference in New Issue
Block a user