Files
Crosspoint/src/ButtonEventManager.cpp
T
2026-04-30 11:08:42 +02:00

129 lines
4.0 KiB
C++

#include "ButtonEventManager.h"
#include "CrossPointSettings.h"
// Required for constexpr array out-of-class definition (C++14).
constexpr ButtonEventManager::Button ButtonEventManager::ALL_BUTTONS[ButtonEventManager::NUM_BUTTONS];
bool ButtonEventManager::hasDoubleAction(const Button button) {
using BA = CrossPointSettings::BUTTON_ACTION;
switch (button) {
case Button::Back:
return SETTINGS.btnDoubleBack != BA::BTN_DEFAULT;
case Button::Confirm:
return SETTINGS.btnDoubleConfirm != BA::BTN_DEFAULT;
case Button::Left:
return SETTINGS.btnDoubleLeft != BA::BTN_DEFAULT;
case Button::Right:
return SETTINGS.btnDoubleRight != BA::BTN_DEFAULT;
case Button::PageBack:
return SETTINGS.btnDoublePageBack != BA::BTN_DEFAULT;
case Button::PageForward:
return SETTINGS.btnDoublePageForward != BA::BTN_DEFAULT;
case Button::Power:
return SETTINGS.btnDoublePower != BA::BTN_DEFAULT;
}
return false;
}
void ButtonEventManager::pushEvent(const Button button, const PressType type) {
const int next = (eventTail + 1) % EVENT_BUF;
if (next == eventHead) return; // buffer full, drop oldest not possible — just drop newest
eventBuf[eventTail] = {button, type};
eventTail = next;
}
void ButtonEventManager::pushEventFront(const Button button, const PressType type) {
const int prev = (eventHead - 1 + EVENT_BUF) % EVENT_BUF;
if (prev == eventTail) return; // buffer full
eventHead = prev;
eventBuf[eventHead] = {button, type};
}
bool ButtonEventManager::consumeEvent(ButtonEvent& out) {
if (eventHead == eventTail) return false;
out = eventBuf[eventHead];
eventHead = (eventHead + 1) % EVENT_BUF;
return true;
}
void ButtonEventManager::drain() {
for (auto& b : buttons) {
b.state = State::Idle;
b.pressDownTime = 0;
b.releaseTime = 0;
}
eventHead = eventTail = 0;
}
void ButtonEventManager::processButton(const int idx, const Button btn) {
PerButton& s = buttons[idx];
const unsigned long now = millis();
const bool pressed = input.wasPressed(btn);
const bool released = input.wasReleased(btn);
const bool held = input.isPressed(btn);
switch (s.state) {
case State::Idle:
if (pressed) {
s.state = State::Pressed;
s.pressDownTime = now;
}
break;
case State::Pressed:
if (released) {
const unsigned long heldMs = now - s.pressDownTime;
if (heldMs >= LONG_PRESS_MS) {
pushEvent(btn, PressType::Long);
s.state = State::Idle;
} else if (hasDoubleAction(btn)) {
// Delay short-press decision until double-click window expires
s.releaseTime = now;
s.state = State::ReleasedOnce;
} else {
// No double action configured — fire immediately
pushEvent(btn, PressType::Short);
s.state = State::Idle;
}
} else if (held && now - s.pressDownTime >= LONG_PRESS_MS) {
// Fire long press immediately once the threshold is reached.
pushEvent(btn, PressType::Long);
s.state = State::Idle;
} else if (!held) {
// Button disappeared without wasReleased edge (e.g. after drain) — reset
s.state = State::Idle;
}
break;
case State::ReleasedOnce:
if (pressed) {
// Second press within window — start tracking it
s.state = State::DoublePressed;
s.pressDownTime = now;
} else if (now - s.releaseTime >= DOUBLE_WINDOW_MS) {
// Window expired without a second press — it was a short press
pushEvent(btn, PressType::Short);
s.state = State::Idle;
}
break;
case State::DoublePressed:
if (released) {
pushEvent(btn, PressType::Double);
s.state = State::Idle;
} else if (!held) {
// Disappeared without edge — treat as double anyway
pushEvent(btn, PressType::Double);
s.state = State::Idle;
}
break;
}
}
void ButtonEventManager::update() {
for (int i = 0; i < NUM_BUTTONS; i++) {
processButton(i, ALL_BUTTONS[i]);
}
}