mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
ed7318c270
## 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
258 lines
9.1 KiB
C++
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;
|
|
}
|
|
|
|
}
|