Files
Crosspoint/src/activities/settings/BleButtonMapActivity.cpp
T
Justin Mitchell 28d0d6f5d2 Replace range-based for loop with std::fill
Use standard algorithm std::fill instead of manual loop to clear bleKeyMap array. This is more idiomatic C++ and expresses intent more clearly.
2026-06-24 17:15:44 -04:00

165 lines
6.2 KiB
C++

#include "BleButtonMapActivity.h"
#include <GfxRenderer.h>
#include <algorithm>
#include <cstdio>
#include <iterator>
#include "BleInput.h"
#include "CrossPointSettings.h"
#include "components/UITheme.h"
#include "fontIds.h"
// Logical functions offered for binding. Page navigation + confirm cover Free2 /
// Free3; the directions are included so a remote can also drive menu navigation.
const BleButtonMapActivity::Fn BleButtonMapActivity::kFunctions[] = {
{MappedInputManager::Button::PageForward, StrId::STR_BT_PAGE_FORWARD},
{MappedInputManager::Button::PageBack, StrId::STR_BT_PAGE_BACK},
{MappedInputManager::Button::Confirm, StrId::STR_CONFIRM},
{MappedInputManager::Button::Back, StrId::STR_BACK},
{MappedInputManager::Button::Up, StrId::STR_DIR_UP},
{MappedInputManager::Button::Down, StrId::STR_DIR_DOWN},
{MappedInputManager::Button::Left, StrId::STR_DIR_LEFT},
{MappedInputManager::Button::Right, StrId::STR_DIR_RIGHT},
};
const uint8_t BleButtonMapActivity::kFunctionCount = static_cast<uint8_t>(sizeof(kFunctions) / sizeof(kFunctions[0]));
void BleButtonMapActivity::onEnter() {
Activity::onEnter();
step = Step::WaitForKey;
capturedKind = 0xFF;
functionIndex = 0;
// Start every mapping session from a clean slate: the user re-maps each remote
// button once, so a button can't be left bound to a stale action and there's no
// separate "clear mappings" step to remember.
std::fill(std::begin(SETTINGS.bleKeyMap), std::end(SETTINGS.bleKeyMap), CrossPointSettings::BleKeyMapEntry{});
SETTINGS.saveToFile();
mappedInput.setBleCaptureMode(true);
requestUpdate();
}
void BleButtonMapActivity::onExit() {
mappedInput.setBleCaptureMode(false);
Activity::onExit();
}
bool BleButtonMapActivity::assignCapturedKey(MappedInputManager::Button button) {
const uint8_t btn = static_cast<uint8_t>(button);
auto& map = SETTINGS.bleKeyMap;
using Entry = CrossPointSettings::BleKeyMapEntry;
const uint8_t kind = capturedKind;
const uint8_t value = capturedValue;
// One key per action: drop any other key currently bound to this action so the same
// action can't be triggered by two different remote buttons.
std::replace_if(
std::begin(map), std::end(map),
[&](const Entry& e) { return e.button == btn && !(e.keyKind == kind && e.keyValue == value); }, Entry{});
// Reuse the slot already bound to this key, else the first free slot.
auto* slot = std::find_if(std::begin(map), std::end(map), [&](const Entry& e) {
return e.button != 0xFF && e.keyKind == kind && e.keyValue == value;
});
if (slot == std::end(map)) {
slot = std::find_if(std::begin(map), std::end(map),
[](const Entry& e) { return e.button == 0xFF || e.keyKind == 0xFF; });
}
if (slot == std::end(map)) return false; // table full
slot->keyKind = kind;
slot->keyValue = value;
slot->button = btn;
SETTINGS.saveToFile();
return true;
}
void BleButtonMapActivity::loop() {
// Front Back button exits the mapping screen at any step.
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
finish();
return;
}
if (step == Step::WaitForKey) {
uint8_t kind = 0xFF;
uint8_t value = 0;
if (mappedInput.takeCapturedBleKey(kind, value)) {
capturedKind = kind;
capturedValue = value;
functionIndex = 0;
step = Step::SelectFunction;
requestUpdate();
}
return;
}
// Step::SelectFunction — pick a logical function for the captured key.
buttonNavigator.onNext([this] {
functionIndex = ButtonNavigator::nextIndex(functionIndex, kFunctionCount);
requestUpdate();
});
buttonNavigator.onPrevious([this] {
functionIndex = ButtonNavigator::previousIndex(functionIndex, kFunctionCount);
requestUpdate();
});
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
assignCapturedKey(kFunctions[functionIndex].button);
// Back to capturing so the user can map (or re-map) the next remote button.
step = Step::WaitForKey;
capturedKind = 0xFF;
requestUpdate();
}
}
void BleButtonMapActivity::render(RenderLock&&) {
renderer.clearScreen();
const auto& metrics = UITheme::getInstance().getMetrics();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_BT_MAP_BUTTONS));
const int topOffset = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - topOffset - metrics.buttonHintsHeight - metrics.verticalSpacing;
if (step == Step::WaitForKey) {
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
tr(STR_BT_PRESS_REMOTE));
// Show the current mappings so the user sees progress.
int row = 0;
for (const auto& e : SETTINGS.bleKeyMap) {
if (e.button == 0xFF) continue;
char keyName[24];
bleinput::describeKey(e.keyKind, e.keyValue, keyName, sizeof(keyName));
const char* fnName = "";
for (uint8_t i = 0; i < kFunctionCount; i++) {
if (static_cast<uint8_t>(kFunctions[i].button) == e.button) {
fnName = I18N.get(kFunctions[i].label);
break;
}
}
char line[64];
snprintf(line, sizeof(line), "%s -> %s", keyName, fnName);
GUI.drawHelpText(renderer, Rect{0, topOffset + row * 22, pageWidth, 20}, line);
row++;
}
} else {
char captured[24];
bleinput::describeKey(capturedKind, capturedValue, captured, sizeof(captured));
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
captured);
GUI.drawList(
renderer, Rect{0, topOffset, pageWidth, contentHeight}, kFunctionCount, functionIndex,
[this](int i) { return std::string(I18N.get(kFunctions[i].label)); }, nullptr, nullptr, nullptr, false);
}
const char* confirm = step == Step::WaitForKey ? "" : tr(STR_SELECT);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirm, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}