diff --git a/src/activities/MenuListActivity.cpp b/src/activities/MenuListActivity.cpp index 46222c19..e5fb1230 100644 --- a/src/activities/MenuListActivity.cpp +++ b/src/activities/MenuListActivity.cpp @@ -27,14 +27,9 @@ void MenuListActivity::onEnter() { } void MenuListActivity::handleNavigation() { - buttonNavigator.onNext([this] { - selectedIndex = buttonNavigator.nextIndex(selectedIndex); - requestUpdate(); - }); - buttonNavigator.onPrevious([this] { - selectedIndex = buttonNavigator.previousIndex(selectedIndex); - requestUpdate(); - }); + const int count = static_cast(menuItems.size()); + buttonNavigator.onNextList(selectedIndex, count, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, count, [this] { requestUpdate(); }); } void MenuListActivity::toggleCurrentItem() { diff --git a/src/activities/browser/OpdsBookBrowserActivity.cpp b/src/activities/browser/OpdsBookBrowserActivity.cpp index 1fa25492..c968727b 100644 --- a/src/activities/browser/OpdsBookBrowserActivity.cpp +++ b/src/activities/browser/OpdsBookBrowserActivity.cpp @@ -15,7 +15,6 @@ #include #include -#include "ButtonEventManager.h" #include "MappedInputManager.h" #include "OpdsFormatLabel.h" #include "activities/network/WifiSelectionActivity.h" @@ -113,9 +112,6 @@ OpdsEntry OpdsBookBrowserActivity::getEntry(size_t index) const { void OpdsBookBrowserActivity::onEnter() { Activity::onEnter(); - globalButtonEvents().forceDoubleAction(ButtonEventManager::Button::Up, true); - globalButtonEvents().forceDoubleAction(ButtonEventManager::Button::Down, true); - state = BrowserState::CHECK_WIFI; entryOffsets.clear(); navigationHistory.clear(); @@ -137,9 +133,6 @@ void OpdsBookBrowserActivity::onEnter() { void OpdsBookBrowserActivity::onExit() { Activity::onExit(); - globalButtonEvents().forceDoubleAction(ButtonEventManager::Button::Up, false); - globalButtonEvents().forceDoubleAction(ButtonEventManager::Button::Down, false); - HalClock::wifiOff(); entryOffsets.clear(); @@ -216,14 +209,10 @@ void OpdsBookBrowserActivity::loop() { return; } - buttonNavigator.onNextRelease([this, entry] { - formatSelectorIndex = ButtonNavigator::nextIndex(formatSelectorIndex, entry.acquisitionLinks.size()); - requestUpdate(); - }); - buttonNavigator.onPreviousRelease([this, entry] { - formatSelectorIndex = ButtonNavigator::previousIndex(formatSelectorIndex, entry.acquisitionLinks.size()); - requestUpdate(); - }); + buttonNavigator.onNextList({MappedInputManager::Button::Down}, formatSelectorIndex, + static_cast(entry.acquisitionLinks.size()), [this] { requestUpdate(); }); + buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, formatSelectorIndex, + static_cast(entry.acquisitionLinks.size()), [this] { requestUpdate(); }); return; } @@ -240,37 +229,11 @@ void OpdsBookBrowserActivity::loop() { } if (!entryOffsets.empty()) { - ButtonEventManager::ButtonEvent extEvent; - while (globalButtonEvents().consumeEvent(extEvent)) { - if (extEvent.type == ButtonEventManager::PressType::Double) { - if (extEvent.button == ButtonEventManager::Button::Down) { - selectorIndex = (selectorIndex + 9) % entryOffsets.size(); - requestUpdate(); - } else if (extEvent.button == ButtonEventManager::Button::Up) { - int size = entryOffsets.size(); - selectorIndex = (selectorIndex - 9 + size) % size; - requestUpdate(); - } - } else if (extEvent.type == ButtonEventManager::PressType::Short || - extEvent.type == ButtonEventManager::PressType::Long) { - if (extEvent.button == ButtonEventManager::Button::Down) { - selectorIndex = ButtonNavigator::nextIndex(selectorIndex, entryOffsets.size()); - requestUpdate(); - } else if (extEvent.button == ButtonEventManager::Button::Up) { - selectorIndex = ButtonNavigator::previousIndex(selectorIndex, entryOffsets.size()); - requestUpdate(); - } - } - } - - buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this] { - selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, entryOffsets.size(), PAGE_ITEMS); - requestUpdate(); - }); - buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this] { - selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, entryOffsets.size(), PAGE_ITEMS); - requestUpdate(); - }); + // Left/Right are reserved for Back and Search, so restrict to Up/Down only. + buttonNavigator.onNextList({MappedInputManager::Button::Down}, selectorIndex, + static_cast(entryOffsets.size()), [this] { requestUpdate(); }); + buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex, + static_cast(entryOffsets.size()), [this] { requestUpdate(); }); } } } diff --git a/src/activities/home/FileBrowserActivity.cpp b/src/activities/home/FileBrowserActivity.cpp index c59f3ade..51e143d1 100644 --- a/src/activities/home/FileBrowserActivity.cpp +++ b/src/activities/home/FileBrowserActivity.cpp @@ -119,7 +119,7 @@ void FileBrowserActivity::onEnter() { if (!focusName.empty()) { const size_t idx = findEntry(focusName); if (idx < files.size()) { - selectorIndex = idx; + selectorIndex = static_cast(idx); } focusName.clear(); } @@ -141,8 +141,6 @@ void FileBrowserActivity::clearFileMetadata(const std::string& fullPath) { } void FileBrowserActivity::loop() { - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); - ButtonEventManager::ButtonEvent ev; while (buttonEvents.consumeEvent(ev)) { if (ev.button == MappedInputManager::Button::Back) { @@ -162,7 +160,7 @@ void FileBrowserActivity::loop() { const auto pos = oldPath.find_last_of('/'); const std::string dirName = oldPath.substr(pos + 1) + "/"; const size_t idx = findEntry(dirName); - selectorIndex = (idx < files.size()) ? idx : 0; + selectorIndex = (idx < files.size()) ? static_cast(idx) : 0; requestUpdate(); } else if (mode == Mode::PickFirmware) { // At root in PickFirmware: cancel back to caller. @@ -249,8 +247,8 @@ void FileBrowserActivity::loop() { loadFiles(); if (files.empty()) { selectorIndex = 0; - } else if (selectorIndex >= files.size()) { - selectorIndex = files.size() - 1; + } else if (selectorIndex >= static_cast(files.size())) { + selectorIndex = static_cast(files.size()) - 1; } requestUpdate(true); } else { @@ -280,27 +278,11 @@ void FileBrowserActivity::loop() { } } - // Up/Down side buttons navigate the list + // Up/Down side buttons navigate the list (Left/Right are reserved for Back/Info actions) const int listSize = static_cast(files.size()); - buttonNavigator.onRelease({MappedInputManager::Button::Down}, [this, listSize] { - selectorIndex = ButtonNavigator::nextIndex(static_cast(selectorIndex), listSize); - requestUpdate(); - }); - - buttonNavigator.onRelease({MappedInputManager::Button::Up}, [this, listSize] { - selectorIndex = ButtonNavigator::previousIndex(static_cast(selectorIndex), listSize); - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this, listSize, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(static_cast(selectorIndex), listSize, pageItems); - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this, listSize, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(static_cast(selectorIndex), listSize, pageItems); - requestUpdate(); - }); + buttonNavigator.onNextList({MappedInputManager::Button::Down}, selectorIndex, listSize, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex, listSize, + [this] { requestUpdate(); }); } std::string getFileName(std::string filename) { diff --git a/src/activities/home/FileBrowserActivity.h b/src/activities/home/FileBrowserActivity.h index 53b04599..e1d10ae0 100644 --- a/src/activities/home/FileBrowserActivity.h +++ b/src/activities/home/FileBrowserActivity.h @@ -19,7 +19,7 @@ class FileBrowserActivity final : public Activity { ButtonNavigator buttonNavigator; - size_t selectorIndex = 0; + int selectorIndex = 0; Mode mode = Mode::Books; diff --git a/src/activities/home/GlobalBookmarksActivity.cpp b/src/activities/home/GlobalBookmarksActivity.cpp index 8295fb6c..6d771309 100644 --- a/src/activities/home/GlobalBookmarksActivity.cpp +++ b/src/activities/home/GlobalBookmarksActivity.cpp @@ -250,39 +250,10 @@ void GlobalBookmarksActivity::loop() { return; } - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); - // Navigator is restricted to Up/Down so the Left (rename) and Right (delete) - // handlers above cannot race default cursor movement on the same release tick. - buttonNavigator.onRelease({MappedInputManager::Button::Down}, [this] { - selectorIndex = buttonNavigator.nextIndex(selectorIndex); - requestUpdate(); - }); - - buttonNavigator.onRelease({MappedInputManager::Button::Up}, [this] { - selectorIndex = buttonNavigator.previousIndex(selectorIndex); - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this, total, pageItems] { - int next = ButtonNavigator::nextPageIndex(selectorIndex, total, pageItems); - if (isSeparatorRow(next)) { - const int adj = ButtonNavigator::nextIndex(next, total, [this](int i) { return !isSeparatorRow(i); }); - if (adj >= 0) next = adj; - } - selectorIndex = next; - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this, total, pageItems] { - int prev = ButtonNavigator::previousPageIndex(selectorIndex, total, pageItems); - if (isSeparatorRow(prev)) { - const int adj = ButtonNavigator::previousIndex(prev, total, [this](int i) { return !isSeparatorRow(i); }); - if (adj >= 0) prev = adj; - } - selectorIndex = prev; - requestUpdate(); - }); + // handlers above cannot race default cursor movement on the same press tick. + buttonNavigator.onNextList({MappedInputManager::Button::Down}, selectorIndex, total, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex, total, [this] { requestUpdate(); }); } void GlobalBookmarksActivity::render(RenderLock&&) { diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index fe09fff3..e0eba6a1 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -38,8 +38,8 @@ void RecentBooksActivity::onEnter() { loadRecentBooks(); selectorIndex = 0; - if (initialFocusIndex >= 0 && static_cast(initialFocusIndex) < recentBooks.size()) { - selectorIndex = static_cast(initialFocusIndex); + if (initialFocusIndex >= 0 && initialFocusIndex < static_cast(recentBooks.size())) { + selectorIndex = initialFocusIndex; } initialFocusIndex = -1; requestUpdate(); @@ -51,13 +51,11 @@ void RecentBooksActivity::onExit() { } void RecentBooksActivity::loop() { - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, true); - ButtonEventManager::ButtonEvent ev; while (buttonEvents.consumeEvent(ev)) { if (ev.button == MappedInputManager::Button::Confirm && (ev.type == ButtonEventManager::PressType::Short || ev.type == ButtonEventManager::PressType::Long)) { - if (recentBooks.empty() || selectorIndex >= recentBooks.size()) { + if (recentBooks.empty() || selectorIndex >= static_cast(recentBooks.size())) { return; } // Long-press Confirm signals "open with KOReader sync" only for EPUBs. @@ -75,7 +73,7 @@ void RecentBooksActivity::loop() { } ReturnHint hint; hint.target = ReturnTo::RecentBooks; - hint.selectIndex = static_cast(selectorIndex); + hint.selectIndex = selectorIndex; activityManager.replaceWithReader(recentBooks[selectorIndex].path, std::move(hint)); return; } @@ -86,7 +84,7 @@ void RecentBooksActivity::loop() { } if (ev.button == MappedInputManager::Button::Left && ev.type == ButtonEventManager::PressType::Short) { - if (recentBooks.empty() || selectorIndex >= recentBooks.size()) return; + if (recentBooks.empty() || selectorIndex >= static_cast(recentBooks.size())) return; const std::string bookPath = recentBooks[selectorIndex].path; const std::string bookTitle = recentBooks[selectorIndex].title; @@ -97,8 +95,8 @@ void RecentBooksActivity::loop() { loadRecentBooks(); if (recentBooks.empty()) { selectorIndex = 0; - } else if (selectorIndex >= recentBooks.size()) { - selectorIndex = recentBooks.size() - 1; + } else if (selectorIndex >= static_cast(recentBooks.size())) { + selectorIndex = static_cast(recentBooks.size()) - 1; } requestUpdate(true); } else { @@ -113,7 +111,7 @@ void RecentBooksActivity::loop() { } if (ev.button == MappedInputManager::Button::Right && ev.type == ButtonEventManager::PressType::Short) { - if (recentBooks.empty() || selectorIndex >= recentBooks.size()) return; + if (recentBooks.empty() || selectorIndex >= static_cast(recentBooks.size())) return; const std::string& path = recentBooks[selectorIndex].path; if (FsHelpers::hasEpubExtension(path) || FsHelpers::hasXtcExtension(path)) { startActivityForResult(std::make_unique(renderer, mappedInput, path), @@ -123,31 +121,12 @@ void RecentBooksActivity::loop() { } } - int listSize = static_cast(recentBooks.size()); - // Navigator is restricted to Up/Down so it cannot race the Left/Right Short - // handlers above: with a double-click action configured, Short events are - // deferred 300ms while wasReleased() is immediate, which would otherwise let - // the cursor move before the custom action runs on the wrong entry. - buttonNavigator.onRelease({MappedInputManager::Button::Down}, [this, listSize] { - selectorIndex = ButtonNavigator::nextIndex(static_cast(selectorIndex), listSize); - requestUpdate(); - }); - - buttonNavigator.onRelease({MappedInputManager::Button::Up}, [this, listSize] { - selectorIndex = ButtonNavigator::previousIndex(static_cast(selectorIndex), listSize); - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this, listSize, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(static_cast(selectorIndex), listSize, pageItems); - requestUpdate(); - }); - - buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this, listSize, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(static_cast(selectorIndex), listSize, pageItems); - requestUpdate(); - }); + // handlers above (Left/Right actions are not in the onNextList/onPreviousList button sets). + const int listSize = static_cast(recentBooks.size()); + buttonNavigator.onNextList({MappedInputManager::Button::Down}, selectorIndex, listSize, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex, listSize, + [this] { requestUpdate(); }); } void RecentBooksActivity::render(RenderLock&&) { diff --git a/src/activities/home/RecentBooksActivity.h b/src/activities/home/RecentBooksActivity.h index 8c028e19..b5044d25 100644 --- a/src/activities/home/RecentBooksActivity.h +++ b/src/activities/home/RecentBooksActivity.h @@ -13,7 +13,7 @@ class RecentBooksActivity final : public Activity { private: ButtonNavigator buttonNavigator; - size_t selectorIndex = 0; + int selectorIndex = 0; int initialFocusIndex = -1; // applied once in onEnter(), then cleared // Recent tab state diff --git a/src/activities/network/NetworkModeSelectionActivity.cpp b/src/activities/network/NetworkModeSelectionActivity.cpp index 4acc5df1..a2b7f200 100644 --- a/src/activities/network/NetworkModeSelectionActivity.cpp +++ b/src/activities/network/NetworkModeSelectionActivity.cpp @@ -43,15 +43,8 @@ void NetworkModeSelectionActivity::loop() { } // Handle navigation - buttonNavigator.onNext([this] { - selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEM_COUNT); - requestUpdate(); - }); - - buttonNavigator.onPrevious([this] { - selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEM_COUNT); - requestUpdate(); - }); + buttonNavigator.onNextList(selectedIndex, MENU_ITEM_COUNT, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, MENU_ITEM_COUNT, [this] { requestUpdate(); }); } void NetworkModeSelectionActivity::render(RenderLock&&) { diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index d373506b..4ebe298b 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -636,15 +636,9 @@ void WifiSelectionActivity::loop() { } // Handle navigation - buttonNavigator.onNext([this] { - selectedNetworkIndex = ButtonNavigator::nextIndex(selectedNetworkIndex, networks.size()); - requestUpdate(); - }); - - buttonNavigator.onPrevious([this] { - selectedNetworkIndex = ButtonNavigator::previousIndex(selectedNetworkIndex, networks.size()); - requestUpdate(); - }); + buttonNavigator.onNextList(selectedNetworkIndex, static_cast(networks.size()), [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedNetworkIndex, static_cast(networks.size()), + [this] { requestUpdate(); }); } } diff --git a/src/activities/network/WifiSelectionActivity.h b/src/activities/network/WifiSelectionActivity.h index 35fb4cf5..62d77a68 100644 --- a/src/activities/network/WifiSelectionActivity.h +++ b/src/activities/network/WifiSelectionActivity.h @@ -48,7 +48,7 @@ class WifiSelectionActivity final : public Activity { ButtonNavigator buttonNavigator; WifiSelectionState state = WifiSelectionState::SCANNING; - size_t selectedNetworkIndex = 0; + int selectedNetworkIndex = 0; std::vector networks; // Selected network for connection diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 386e4a55..2e5b8f3a 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -78,25 +78,8 @@ void EpubReaderChapterSelectionActivity::loop() { } } - buttonNavigator.onNextRelease([this, totalItems] { - selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this, totalItems] { - selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onNextContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); + buttonNavigator.onNextList(selectorIndex, totalItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectorIndex, totalItems, [this] { requestUpdate(); }); } void EpubReaderChapterSelectionActivity::render(RenderLock&&) { diff --git a/src/activities/reader/MdReaderTocSelectionActivity.cpp b/src/activities/reader/MdReaderTocSelectionActivity.cpp index 771f634f..575a789d 100644 --- a/src/activities/reader/MdReaderTocSelectionActivity.cpp +++ b/src/activities/reader/MdReaderTocSelectionActivity.cpp @@ -71,25 +71,8 @@ void MdReaderTocSelectionActivity::loop() { } } - buttonNavigator.onNextRelease([this, totalItems] { - selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this, totalItems] { - selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onNextContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); + buttonNavigator.onNextList(selectorIndex, totalItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectorIndex, totalItems, [this] { requestUpdate(); }); } void MdReaderTocSelectionActivity::render(RenderLock&&) { diff --git a/src/activities/reader/StarredPagesActivity.cpp b/src/activities/reader/StarredPagesActivity.cpp index 1fcd8715..8cf76213 100644 --- a/src/activities/reader/StarredPagesActivity.cpp +++ b/src/activities/reader/StarredPagesActivity.cpp @@ -113,27 +113,8 @@ void StarredPagesActivity::loop() { if (totalItems == 0) return; - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); - - buttonNavigator.onNextRelease([this, totalItems] { - selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this, totalItems] { - selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onNextContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); + buttonNavigator.onNextList(selectorIndex, totalItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectorIndex, totalItems, [this] { requestUpdate(); }); } void StarredPagesActivity::render(RenderLock&&) { diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index cab385db..b932771a 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -85,25 +85,8 @@ void XtcReaderChapterSelectionActivity::loop() { } } - buttonNavigator.onNextRelease([this, totalItems] { - selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this, totalItems] { - selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); - requestUpdate(); - }); - - buttonNavigator.onNextContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { - selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); - requestUpdate(); - }); + buttonNavigator.onNextList(selectorIndex, totalItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectorIndex, totalItems, [this] { requestUpdate(); }); } void XtcReaderChapterSelectionActivity::render(RenderLock&&) { diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index dad2a6a4..a1edcec9 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -481,19 +481,8 @@ void FontDownloadActivity::loop() { return; } - buttonNavigator_.onNextRelease([this] { - if (selectedIndex_ < listItemCount() - 1) { - selectedIndex_++; - requestUpdate(); - } - }); - - buttonNavigator_.onPreviousRelease([this] { - if (selectedIndex_ > 0) { - selectedIndex_--; - requestUpdate(); - } - }); + buttonNavigator_.onNextList(selectedIndex_, listItemCount(), [this] { requestUpdate(); }); + buttonNavigator_.onPreviousList(selectedIndex_, listItemCount(), [this] { requestUpdate(); }); if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (!families_.empty()) { diff --git a/src/activities/settings/FontSelectionActivity.cpp b/src/activities/settings/FontSelectionActivity.cpp index 81881e9c..bd609b6d 100644 --- a/src/activities/settings/FontSelectionActivity.cpp +++ b/src/activities/settings/FontSelectionActivity.cpp @@ -45,15 +45,8 @@ void FontSelectionActivity::loop() { return; } - buttonNavigator.onNextRelease([this] { - selectedIndex = ButtonNavigator::nextIndex(selectedIndex, fontCount); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this] { - selectedIndex = ButtonNavigator::previousIndex(selectedIndex, fontCount); - requestUpdate(); - }); + buttonNavigator.onNextList(selectedIndex, fontCount, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, fontCount, [this] { requestUpdate(); }); } void FontSelectionActivity::handleSelection() { diff --git a/src/activities/settings/LanguageSelectActivity.cpp b/src/activities/settings/LanguageSelectActivity.cpp index 2f09ae35..2e3486a0 100644 --- a/src/activities/settings/LanguageSelectActivity.cpp +++ b/src/activities/settings/LanguageSelectActivity.cpp @@ -37,15 +37,8 @@ void LanguageSelectActivity::loop() { } // Handle navigation - buttonNavigator.onNextRelease([this] { - selectedIndex = ButtonNavigator::nextIndex(static_cast(selectedIndex), totalItems); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this] { - selectedIndex = ButtonNavigator::previousIndex(static_cast(selectedIndex), totalItems); - requestUpdate(); - }); + buttonNavigator.onNextList(selectedIndex, totalItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, totalItems, [this] { requestUpdate(); }); } void LanguageSelectActivity::handleSelection() { diff --git a/src/activities/settings/OpdsServerListActivity.cpp b/src/activities/settings/OpdsServerListActivity.cpp index d6964107..30fb6d9a 100644 --- a/src/activities/settings/OpdsServerListActivity.cpp +++ b/src/activities/settings/OpdsServerListActivity.cpp @@ -49,17 +49,8 @@ void OpdsServerListActivity::loop() { } const int itemCount = getItemCount(); - if (itemCount > 0) { - buttonNavigator.onNext([this, itemCount] { - selectedIndex = ButtonNavigator::nextIndex(selectedIndex, itemCount); - requestUpdate(); - }); - - buttonNavigator.onPrevious([this, itemCount] { - selectedIndex = ButtonNavigator::previousIndex(selectedIndex, itemCount); - requestUpdate(); - }); - } + buttonNavigator.onNextList(selectedIndex, itemCount, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, itemCount, [this] { requestUpdate(); }); } void OpdsServerListActivity::handleSelection() { diff --git a/src/activities/settings/OpdsSettingsActivity.cpp b/src/activities/settings/OpdsSettingsActivity.cpp index c3907cea..aa84c474 100644 --- a/src/activities/settings/OpdsSettingsActivity.cpp +++ b/src/activities/settings/OpdsSettingsActivity.cpp @@ -61,15 +61,8 @@ void OpdsSettingsActivity::loop() { } const int menuItems = getMenuItemCount(); - buttonNavigator.onNext([this, menuItems] { - selectedIndex = (selectedIndex + 1) % menuItems; - requestUpdate(); - }); - - buttonNavigator.onPrevious([this, menuItems] { - selectedIndex = (selectedIndex + menuItems - 1) % menuItems; - requestUpdate(); - }); + buttonNavigator.onNextList(selectedIndex, menuItems, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, menuItems, [this] { requestUpdate(); }); } bool OpdsSettingsActivity::saveServer() { @@ -206,7 +199,7 @@ void OpdsSettingsActivity::render(RenderLock&&) { StrId::STR_PASSWORD}; GUI.drawList( - renderer, Rect{0, contentTop, pageWidth, contentHeight}, menuItems, static_cast(selectedIndex), + renderer, Rect{0, contentTop, pageWidth, contentHeight}, menuItems, selectedIndex, [this, &fieldNames](int index) { if (index < BASE_ITEMS) { return std::string(I18N.get(fieldNames[index])); diff --git a/src/activities/settings/OpdsSettingsActivity.h b/src/activities/settings/OpdsSettingsActivity.h index 2e818edc..b84bdf87 100644 --- a/src/activities/settings/OpdsSettingsActivity.h +++ b/src/activities/settings/OpdsSettingsActivity.h @@ -25,7 +25,7 @@ class OpdsSettingsActivity final : public Activity { private: ButtonNavigator buttonNavigator; - size_t selectedIndex = 0; + int selectedIndex = 0; int serverIndex; OpdsServer editServer; bool isNewServer = false; diff --git a/src/activities/settings/StatusBarSettingsActivity.cpp b/src/activities/settings/StatusBarSettingsActivity.cpp index 11bbf541..eeb731c5 100644 --- a/src/activities/settings/StatusBarSettingsActivity.cpp +++ b/src/activities/settings/StatusBarSettingsActivity.cpp @@ -197,29 +197,9 @@ void StatusBarSettingsActivity::loop() { } // Handle navigation - buttonNavigator.onNextRelease([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; - selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount); - requestUpdate(); - }); - - buttonNavigator.onPreviousRelease([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; - selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount); - requestUpdate(); - }); - - buttonNavigator.onNextContinuous([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; - selectedIndex = ButtonNavigator::nextIndex(selectedIndex, menuCount); - requestUpdate(); - }); - - buttonNavigator.onPreviousContinuous([this] { - const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; - selectedIndex = ButtonNavigator::previousIndex(selectedIndex, menuCount); - requestUpdate(); - }); + const int menuCount = SETTINGS.useClock ? MENU_ITEMS_WITH_CLOCK : MENU_ITEMS_NO_CLOCK; + buttonNavigator.onNextList(selectedIndex, menuCount, [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, menuCount, [this] { requestUpdate(); }); } void StatusBarSettingsActivity::handleSelection() { diff --git a/src/activities/weather/WeatherSettingsActivity.cpp b/src/activities/weather/WeatherSettingsActivity.cpp index 371eb3c6..9224ad4e 100644 --- a/src/activities/weather/WeatherSettingsActivity.cpp +++ b/src/activities/weather/WeatherSettingsActivity.cpp @@ -55,18 +55,8 @@ void WeatherSettingsActivity::loop() { return; } - buttonNavigator.onNext([this] { - if (searchResults.empty()) return; - selectedIndex = (selectedIndex + 1) % static_cast(searchResults.size()); - requestUpdate(); - }); - - buttonNavigator.onPrevious([this] { - if (searchResults.empty()) return; - const int size = static_cast(searchResults.size()); - selectedIndex = (selectedIndex + size - 1) % size; - requestUpdate(); - }); + buttonNavigator.onNextList(selectedIndex, static_cast(searchResults.size()), [this] { requestUpdate(); }); + buttonNavigator.onPreviousList(selectedIndex, static_cast(searchResults.size()), [this] { requestUpdate(); }); return; } diff --git a/src/util/ButtonNavigator.cpp b/src/util/ButtonNavigator.cpp index 3fc1714b..1a901551 100644 --- a/src/util/ButtonNavigator.cpp +++ b/src/util/ButtonNavigator.cpp @@ -224,3 +224,85 @@ int ButtonNavigator::previousPageIndex(const int currentIndex, const int totalIt return lastPageIndex * itemsPerPage; } + +void ButtonNavigator::onNextList(int& selectedIndex, const int totalItems, const Callback& onChange) { + onListNav(getNextButtons(), true, selectedIndex, totalItems, lastNextPressMs, longPressNextFired, onChange); +} + +void ButtonNavigator::onNextList(const Buttons& buttons, int& selectedIndex, const int totalItems, + const Callback& onChange) { + onListNav(buttons, true, selectedIndex, totalItems, lastNextPressMs, longPressNextFired, onChange); +} + +void ButtonNavigator::onPreviousList(int& selectedIndex, const int totalItems, const Callback& onChange) { + onListNav(getPreviousButtons(), false, selectedIndex, totalItems, lastPreviousPressMs, longPressPreviousFired, + onChange); +} + +void ButtonNavigator::onPreviousList(const Buttons& buttons, int& selectedIndex, const int totalItems, + const Callback& onChange) { + onListNav(buttons, false, selectedIndex, totalItems, lastPreviousPressMs, longPressPreviousFired, onChange); +} + +void ButtonNavigator::onListNav(const Buttons& buttons, const bool forward, int& selectedIndex, const int totalItems, + uint32_t& lastPressMs, bool& longPressFired, const Callback& onChange) { + if (!mappedInput || totalItems <= 0) return; + + const bool anyHeld = std::any_of(buttons.begin(), buttons.end(), + [](const MappedInputManager::Button b) { return mappedInput->isPressed(b); }); + + if (anyHeld && mappedInput->getHeldTime() > listLongPressMs) { + if (!longPressFired) { + longPressFired = true; + if (forward) { + selectedIndex = totalItems - 1; + if (selectablePredicate) { + while (selectedIndex > 0 && !selectablePredicate(selectedIndex)) --selectedIndex; + } + } else { + selectedIndex = 0; + if (selectablePredicate) { + while (selectedIndex < totalItems - 1 && !selectablePredicate(selectedIndex)) ++selectedIndex; + } + } + onChange(); + } + return; + } + + const bool wasReleased = std::any_of(buttons.begin(), buttons.end(), + [](const MappedInputManager::Button b) { return mappedInput->wasReleased(b); }); + if (wasReleased) { + longPressFired = false; + return; + } + + const bool wasPressed = std::any_of(buttons.begin(), buttons.end(), + [](const MappedInputManager::Button b) { return mappedInput->wasPressed(b); }); + if (!wasPressed) return; + + const uint32_t now = millis(); + const bool isDouble = (now - lastPressMs) < listDoubleClickMs; + lastPressMs = now; + + if (isDouble) { + // Restore to position before the first press so the total movement is exactly listJumpCount. + // Guard against a stale indexBeforePress if totalItems shrank since the single press stored it. + selectedIndex = (indexBeforePress >= 0 && indexBeforePress < totalItems) ? indexBeforePress : selectedIndex; + for (int i = 0; i < listJumpCount; ++i) { + const int next = + forward ? (selectablePredicate ? nextIndex(selectedIndex) : nextIndex(selectedIndex, totalItems)) + : (selectablePredicate ? previousIndex(selectedIndex) : previousIndex(selectedIndex, totalItems)); + // Stop before wrapping: forward movement decreases index only on wrap; backward vice versa. + if (forward && next <= selectedIndex) break; + if (!forward && next >= selectedIndex) break; + selectedIndex = next; + } + } else { + indexBeforePress = selectedIndex; + selectedIndex = + forward ? (selectablePredicate ? nextIndex(selectedIndex) : nextIndex(selectedIndex, totalItems)) + : (selectablePredicate ? previousIndex(selectedIndex) : previousIndex(selectedIndex, totalItems)); + } + onChange(); +} diff --git a/src/util/ButtonNavigator.h b/src/util/ButtonNavigator.h index b76df714..c47adc25 100644 --- a/src/util/ButtonNavigator.h +++ b/src/util/ButtonNavigator.h @@ -16,7 +16,19 @@ class ButtonNavigator final { std::function selectablePredicate; int selectableTotalItems = 0; + uint32_t lastNextPressMs = 0; + uint32_t lastPreviousPressMs = 0; + bool longPressNextFired = false; + bool longPressPreviousFired = false; + int indexBeforePress = 0; + + static constexpr uint16_t listDoubleClickMs = 350; + static constexpr uint32_t listLongPressMs = 1500; + static constexpr int listJumpCount = 10; + [[nodiscard]] bool shouldNavigateContinuously() const; + void onListNav(const Buttons& buttons, bool forward, int& selectedIndex, int totalItems, uint32_t& lastPressMs, + bool& longPressFired, const Callback& onChange); public: explicit ButtonNavigator(const uint16_t continuousIntervalMs = 500, const uint16_t continuousStartMs = 500) @@ -57,6 +69,13 @@ class ButtonNavigator final { [[nodiscard]] static int nextPageIndex(int currentIndex, int totalItems, int itemsPerPage); [[nodiscard]] static int previousPageIndex(int currentIndex, int totalItems, int itemsPerPage); + // List navigation with double-click (skip 10) and long-press (jump to edge). + // Replaces the typical onNext/onPrevious + onContinuous pattern for drawList consumers. + void onNextList(int& selectedIndex, int totalItems, const Callback& onChange); + void onNextList(const Buttons& buttons, int& selectedIndex, int totalItems, const Callback& onChange); + void onPreviousList(int& selectedIndex, int totalItems, const Callback& onChange); + void onPreviousList(const Buttons& buttons, int& selectedIndex, int totalItems, const Callback& onChange); + [[nodiscard]] static Buttons getNextButtons() { return {MappedInputManager::Button::Down, MappedInputManager::Button::Right}; }