Files
spice2x.github.io/src/spice2x/overlay/windows/controller_presets.cpp
T
bicarus ed7318c270 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
2026-07-21 00:26:36 -07:00

258 lines
9.1 KiB
C++

#include "controller_presets.h"
#include <algorithm>
#include "cfg/button.h"
namespace overlay::windows {
// 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++;
if (btn.primary.is_device()) device++;
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) naive++;
if (alt.is_device()) device++;
}
}
}
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;
}
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;
}
int ControllerTemplate::count_device_analogs() const {
int count = 0;
for (auto &a : analogs) {
if (a.is_device()) count++;
}
return count;
}
int ControllerTemplate::count_device_lights() const {
int count = 0;
for (auto &l : lights) {
if (l.primary.is_device()) count++;
for (auto &alt : l.alternatives) {
if (alt.is_device()) count++;
}
}
return count;
}
static void collect_devices_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &devices) {
for (auto &btn : btns) {
if (btn.primary.is_device()) devices.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
}
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);
}
for (auto &l : lights) {
if (l.primary.is_device()) devices.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) devices.insert(alt.device_identifier);
}
}
return devices;
}
static void collect_sources_from(const std::vector<TemplateButtonBinding> &btns,
std::set<std::string> &sources_set, bool &has_naive) {
for (auto &btn : btns) {
if (btn.primary.is_naive()) has_naive = true;
if (btn.primary.is_device()) sources_set.insert(btn.primary.device_identifier);
for (auto &alt : btn.alternatives) {
if (alt.is_naive()) has_naive = true;
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
}
std::vector<std::string> ControllerTemplate::get_binding_sources() const {
std::set<std::string> sources_set;
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()) {
sources_set.insert(a.device_identifier);
}
}
for (auto &l : lights) {
if (l.primary.is_device()) sources_set.insert(l.primary.device_identifier);
for (auto &alt : l.alternatives) {
if (alt.is_device()) sources_set.insert(alt.device_identifier);
}
}
std::vector<std::string> result;
if (has_naive) {
result.emplace_back("Naive");
}
for (auto &s : sources_set) {
result.push_back(s);
}
// sort alphabetically
std::sort(result.begin(), result.end());
return result;
}
void ControllerTemplate::rename_source(const std::string &old_id, const std::string &new_id) {
auto rename_in_buttons = [&](std::vector<TemplateButtonBinding> &btns) {
for (auto &btn : btns) {
if (btn.primary.device_identifier == old_id) btn.primary.device_identifier = new_id;
for (auto &alt : btn.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
};
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;
}
for (auto &l : lights) {
if (l.primary.device_identifier == old_id) l.primary.device_identifier = new_id;
for (auto &alt : l.alternatives) {
if (alt.device_identifier == old_id) alt.device_identifier = new_id;
}
}
}
std::string ControllerTemplate::get_source_summary(const std::string &source) const {
bool is_naive = (source == "Naive");
std::string result;
// collect button lines from a button vector
auto collect_btn_lines = [&](const std::vector<TemplateButtonBinding> &btns,
std::vector<std::string> &lines) {
for (auto &tb : btns) {
auto check_entry = [&](const TemplateButtonEntry &e) {
if (is_naive && e.is_naive()) {
Button b("");
b.setVKey(e.vKey);
lines.push_back(tb.name + " = " + (e.vKey == INVALID_VKEY ? "None" : b.getVKeyString()));
} else if (!is_naive && e.is_device() && e.device_identifier == source) {
std::string desc = std::to_string(e.vKey);
if (e.analog_type != BAT_NONE) {
desc += " (analog:" + std::to_string((int)e.analog_type) + ")";
}
lines.push_back(tb.name + " = " + desc);
}
};
check_entry(tb.primary);
for (auto &alt : tb.alternatives) {
check_entry(alt);
}
}
};
// buttons
std::vector<std::string> btn_lines;
collect_btn_lines(buttons, btn_lines);
if (!btn_lines.empty()) {
result += "Buttons:\n";
for (auto &line : btn_lines) {
result += " " + line + "\n";
}
}
// 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);
if (!kp_lines.empty()) {
result += "Keypad:\n";
for (auto &line : kp_lines) {
result += " " + line + "\n";
}
}
// analogs
if (!is_naive) {
std::vector<std::string> analog_lines;
for (auto &a : analogs) {
if (a.is_device() && a.device_identifier == source) {
analog_lines.push_back(a.name + " (idx:" + std::to_string(a.index) + ")");
}
}
if (!analog_lines.empty()) {
result += "Analogs:\n";
for (auto &line : analog_lines) {
result += " " + line + "\n";
}
}
}
// lights
if (!is_naive) {
std::vector<std::string> light_lines;
for (auto &l : lights) {
auto check_light = [&](const TemplateLightEntry &e) {
if (e.is_device() && e.device_identifier == source) {
light_lines.push_back(l.name + " (idx:" + std::to_string(e.index) + ")");
}
};
check_light(l.primary);
for (auto &alt : l.alternatives) {
check_light(alt);
}
}
if (!light_lines.empty()) {
result += "Lights:\n";
for (auto &line : light_lines) {
result += " " + line + "\n";
}
}
}
if (result.empty()) {
result = "(no bindings)";
}
// trim trailing newline
while (!result.empty() && result.back() == '\n') {
result.pop_back();
}
return result;
}
}