mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
refactor: implement state machine for button press detection in MyLibraryActivity
Button Behavior Matrix: ┌─────────────────┬──────────────┬──────────────────┐ │ Action │ Condition │ Timing │ ├─────────────────┼──────────────┼──────────────────┤ │ Switch Tab │ Long press │ ≥450ms hold │ │ Skip Page │ Double press │ <120ms between │ │ │ │ 1st PRESS & │ │ │ │ 2nd RELEASE │ │ Move Item │ Short press │ <450ms press │ └─────────────────┴──────────────┴──────────────────┘ Key Fixes: - Each action has clear, exclusive condition
This commit is contained in:
parent
ebcd813ff6
commit
38b17ec95d
@ -19,9 +19,10 @@ constexpr int LINE_HEIGHT = 30;
|
|||||||
constexpr int LEFT_MARGIN = 20;
|
constexpr int LEFT_MARGIN = 20;
|
||||||
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator
|
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator
|
||||||
|
|
||||||
// Timing thresholds
|
// Timing thresholds for button behavior
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
constexpr int LONG_PRESS_MS = 450; // Long press: change tab
|
||||||
constexpr unsigned long GO_HOME_MS = 1000;
|
constexpr int DOUBLE_PRESS_MS = 120; // Double press: skip page
|
||||||
|
constexpr unsigned long GO_HOME_MS = 1000; // Long press back: go to root
|
||||||
|
|
||||||
void sortFileList(std::vector<std::string>& strs) {
|
void sortFileList(std::vector<std::string>& strs) {
|
||||||
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
||||||
@ -143,6 +144,51 @@ void MyLibraryActivity::taskTrampoline(void* param) {
|
|||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Action execution: Move one item (short press timeout)
|
||||||
|
void MyLibraryActivity::executeMoveItem(bool isPrevButton) {
|
||||||
|
const int itemCount = getCurrentItemCount();
|
||||||
|
if (itemCount > 0) {
|
||||||
|
if (isPrevButton) {
|
||||||
|
selectorIndex = (selectorIndex + itemCount - 1) % itemCount;
|
||||||
|
} else {
|
||||||
|
selectorIndex = (selectorIndex + 1) % itemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action execution: Skip page (double press)
|
||||||
|
void MyLibraryActivity::executeSkipPage(bool isPrevButton) {
|
||||||
|
const int itemCount = getCurrentItemCount();
|
||||||
|
const int pageItems = getPageItems();
|
||||||
|
if (itemCount > 0) {
|
||||||
|
if (isPrevButton) {
|
||||||
|
int targetPage = (selectorIndex / pageItems) - 1;
|
||||||
|
if (targetPage < 0) {
|
||||||
|
targetPage = ((itemCount - 1) / pageItems);
|
||||||
|
}
|
||||||
|
selectorIndex = targetPage * pageItems;
|
||||||
|
} else {
|
||||||
|
int targetPage = (selectorIndex / pageItems) + 1;
|
||||||
|
int maxPage = (itemCount - 1) / pageItems;
|
||||||
|
if (targetPage > maxPage) {
|
||||||
|
targetPage = 0;
|
||||||
|
}
|
||||||
|
selectorIndex = targetPage * pageItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action execution: Switch tab (long press)
|
||||||
|
void MyLibraryActivity::executeSwitchTab(bool isPrevButton) {
|
||||||
|
if (isPrevButton && currentTab == Tab::Files) {
|
||||||
|
currentTab = Tab::Recent;
|
||||||
|
selectorIndex = 0;
|
||||||
|
} else if (!isPrevButton && currentTab == Tab::Recent) {
|
||||||
|
currentTab = Tab::Files;
|
||||||
|
selectorIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyLibraryActivity::onEnter() {
|
void MyLibraryActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
|
||||||
@ -185,6 +231,9 @@ void MyLibraryActivity::loop() {
|
|||||||
const int itemCount = getCurrentItemCount();
|
const int itemCount = getCurrentItemCount();
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
|
|
||||||
|
// Get current time for all timing operations
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
// Long press BACK (1s+) in Files tab goes to root folder
|
// Long press BACK (1s+) in Files tab goes to root folder
|
||||||
if (currentTab == Tab::Files && mappedInput.isPressed(MappedInputManager::Button::Back) &&
|
if (currentTab == Tab::Files && mappedInput.isPressed(MappedInputManager::Button::Back) &&
|
||||||
mappedInput.getHeldTime() >= GO_HOME_MS) {
|
mappedInput.getHeldTime() >= GO_HOME_MS) {
|
||||||
@ -197,13 +246,6 @@ void MyLibraryActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up);
|
|
||||||
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down);
|
|
||||||
const bool leftReleased = mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
||||||
const bool rightReleased = mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
|
|
||||||
// Confirm button - open selected item
|
// Confirm button - open selected item
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (currentTab == Tab::Recent) {
|
if (currentTab == Tab::Recent) {
|
||||||
@ -253,38 +295,84 @@ void MyLibraryActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab switching: Left/Right always control tabs
|
// Navigation buttons (UP/LEFT and DOWN/RIGHT have same behavior)
|
||||||
if (leftReleased && currentTab == Tab::Files) {
|
const bool upPressed = mappedInput.isPressed(MappedInputManager::Button::Up);
|
||||||
currentTab = Tab::Recent;
|
const bool leftPressed = mappedInput.isPressed(MappedInputManager::Button::Left);
|
||||||
selectorIndex = 0;
|
const bool downPressed = mappedInput.isPressed(MappedInputManager::Button::Down);
|
||||||
updateRequired = true;
|
const bool rightPressed = mappedInput.isPressed(MappedInputManager::Button::Right);
|
||||||
return;
|
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up);
|
||||||
|
const bool leftReleased = mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
|
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down);
|
||||||
|
const bool rightReleased = mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
|
// Navigation: UP/LEFT move backward, DOWN/RIGHT move forward
|
||||||
|
const bool prevPressed = upPressed || leftPressed;
|
||||||
|
const bool nextPressed = downPressed || rightPressed;
|
||||||
|
const bool prevReleased = upReleased || leftReleased;
|
||||||
|
const bool nextReleased = downReleased || rightReleased;
|
||||||
|
|
||||||
|
// State machine for button press detection
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
// IDLE: Wait for first press
|
||||||
|
if (buttonState == ButtonState::Idle) {
|
||||||
|
if (prevPressed || nextPressed) {
|
||||||
|
buttonState = ButtonState::FirstPress;
|
||||||
|
firstPressTime = currentTime;
|
||||||
|
isPrevButtonPressed = prevPressed;
|
||||||
}
|
}
|
||||||
if (rightReleased && currentTab == Tab::Recent) {
|
}
|
||||||
currentTab = Tab::Files;
|
// FIRST_PRESS: Button is held, check for long press or release
|
||||||
selectorIndex = 0;
|
else if (buttonState == ButtonState::FirstPress) {
|
||||||
|
const unsigned long holdDuration = currentTime - firstPressTime;
|
||||||
|
|
||||||
|
// Check for long press (>=450ms) - switch tab
|
||||||
|
if (holdDuration >= LONG_PRESS_MS) {
|
||||||
|
executeSwitchTab(isPrevButtonPressed);
|
||||||
|
buttonState = ButtonState::WaitingForReleaseAfterLongPress;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation: Up/Down moves through items only
|
// Check for release (<450ms) - transition to waiting for second press
|
||||||
const bool prevReleased = upReleased;
|
if ((isPrevButtonPressed && prevReleased) || (!isPrevButtonPressed && nextReleased)) {
|
||||||
const bool nextReleased = downReleased;
|
buttonState = ButtonState::WaitingForSecondPress;
|
||||||
|
firstReleaseTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// WAITING_FOR_SECOND_PRESS: First button released, waiting for second press
|
||||||
|
else if (buttonState == ButtonState::WaitingForSecondPress) {
|
||||||
|
const unsigned long waitDuration = currentTime - firstReleaseTime;
|
||||||
|
|
||||||
if (prevReleased && itemCount > 0) {
|
// Timeout (>=120ms without second press) - execute move_item
|
||||||
if (skipPage) {
|
if (waitDuration >= DOUBLE_PRESS_MS) {
|
||||||
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + itemCount) % itemCount;
|
executeMoveItem(isPrevButtonPressed);
|
||||||
} else {
|
buttonState = ButtonState::Idle;
|
||||||
selectorIndex = (selectorIndex + itemCount - 1) % itemCount;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextReleased && itemCount > 0) {
|
return;
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % itemCount;
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + 1) % itemCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second press detected (<120ms) - double press
|
||||||
|
if (prevPressed || nextPressed) {
|
||||||
|
buttonState = ButtonState::DoublePressDetected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DOUBLE_PRESS_DETECTED: Second press detected, wait for release
|
||||||
|
else if (buttonState == ButtonState::DoublePressDetected) {
|
||||||
|
// Wait for second button release
|
||||||
|
if (prevReleased || nextReleased) {
|
||||||
|
executeSkipPage(isPrevButtonPressed);
|
||||||
|
buttonState = ButtonState::Idle;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// WAITING_FOR_RELEASE_AFTER_LONG_PRESS: Ignore release after long press
|
||||||
|
else if (buttonState == ButtonState::WaitingForReleaseAfterLongPress) {
|
||||||
|
// Wait for button to be released, then go back to idle
|
||||||
|
if ((isPrevButtonPressed && prevReleased) || (!isPrevButtonPressed && nextReleased)) {
|
||||||
|
buttonState = ButtonState::Idle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,25 @@ class MyLibraryActivity final : public Activity {
|
|||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|
||||||
|
// State machine for button press detection
|
||||||
|
enum class ButtonState {
|
||||||
|
Idle,
|
||||||
|
FirstPress,
|
||||||
|
WaitingForSecondPress,
|
||||||
|
DoublePressDetected,
|
||||||
|
WaitingForReleaseAfterLongPress
|
||||||
|
};
|
||||||
|
|
||||||
|
ButtonState buttonState = ButtonState::Idle;
|
||||||
|
unsigned long firstPressTime = 0; // Time of first PRESS
|
||||||
|
unsigned long firstReleaseTime = 0; // Time of first RELEASE
|
||||||
|
bool isPrevButtonPressed = false; // Which button was pressed first
|
||||||
|
|
||||||
|
// Action execution functions
|
||||||
|
void executeMoveItem(bool isPrevButton);
|
||||||
|
void executeSkipPage(bool isPrevButton);
|
||||||
|
void executeSwitchTab(bool isPrevButton);
|
||||||
|
|
||||||
// Recent tab state
|
// Recent tab state
|
||||||
std::vector<std::string> bookTitles; // Display titles for each book
|
std::vector<std::string> bookTitles; // Display titles for each book
|
||||||
std::vector<std::string> bookPaths; // Paths for each visible book (excludes missing)
|
std::vector<std::string> bookPaths; // Paths for each visible book (excludes missing)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user