Add double and long clicks in lists

This commit is contained in:
jpirnay
2026-05-13 14:00:07 +02:00
parent d337110649
commit c8c697da06
24 changed files with 169 additions and 333 deletions
+3 -8
View File
@@ -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<int>(menuItems.size());
buttonNavigator.onNextList(selectedIndex, count, [this] { requestUpdate(); });
buttonNavigator.onPreviousList(selectedIndex, count, [this] { requestUpdate(); });
}
void MenuListActivity::toggleCurrentItem() {
@@ -15,7 +15,6 @@
#include <cstdio>
#include <memory>
#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(formatSelectorIndex, static_cast<int>(entry.acquisitionLinks.size()),
[this] { requestUpdate(); });
buttonNavigator.onPreviousList(formatSelectorIndex, static_cast<int>(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<int>(entryOffsets.size()), [this] { requestUpdate(); });
buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex,
static_cast<int>(entryOffsets.size()), [this] { requestUpdate(); });
}
}
}
+8 -26
View File
@@ -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<int>(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<int>(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<int>(files.size())) {
selectorIndex = static_cast<int>(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<int>(files.size());
buttonNavigator.onRelease({MappedInputManager::Button::Down}, [this, listSize] {
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onRelease({MappedInputManager::Button::Up}, [this, listSize] {
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this, listSize, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
requestUpdate();
});
buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this, listSize, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(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) {
+1 -1
View File
@@ -19,7 +19,7 @@ class FileBrowserActivity final : public Activity {
ButtonNavigator buttonNavigator;
size_t selectorIndex = 0;
int selectorIndex = 0;
Mode mode = Mode::Books;
@@ -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&&) {
+13 -34
View File
@@ -38,8 +38,8 @@ void RecentBooksActivity::onEnter() {
loadRecentBooks();
selectorIndex = 0;
if (initialFocusIndex >= 0 && static_cast<size_t>(initialFocusIndex) < recentBooks.size()) {
selectorIndex = static_cast<size_t>(initialFocusIndex);
if (initialFocusIndex >= 0 && initialFocusIndex < static_cast<int>(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<int>(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<int>(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<int>(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<int>(recentBooks.size())) {
selectorIndex = static_cast<int>(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<int>(recentBooks.size())) return;
const std::string& path = recentBooks[selectorIndex].path;
if (FsHelpers::hasEpubExtension(path) || FsHelpers::hasXtcExtension(path)) {
startActivityForResult(std::make_unique<BookInfoActivity>(renderer, mappedInput, path),
@@ -123,31 +121,12 @@ void RecentBooksActivity::loop() {
}
}
int listSize = static_cast<int>(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<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onRelease({MappedInputManager::Button::Up}, [this, listSize] {
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onContinuous({MappedInputManager::Button::Down}, [this, listSize, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
requestUpdate();
});
buttonNavigator.onContinuous({MappedInputManager::Button::Up}, [this, listSize, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
requestUpdate();
});
// handlers above (Left/Right actions are not in the onNextList/onPreviousList button sets).
const int listSize = static_cast<int>(recentBooks.size());
buttonNavigator.onNextList({MappedInputManager::Button::Down}, selectorIndex, listSize, [this] { requestUpdate(); });
buttonNavigator.onPreviousList({MappedInputManager::Button::Up}, selectorIndex, listSize,
[this] { requestUpdate(); });
}
void RecentBooksActivity::render(RenderLock&&) {
+1 -1
View File
@@ -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
@@ -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&&) {
@@ -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<int>(networks.size()), [this] { requestUpdate(); });
buttonNavigator.onPreviousList(selectedNetworkIndex, static_cast<int>(networks.size()),
[this] { requestUpdate(); });
}
}
@@ -48,7 +48,7 @@ class WifiSelectionActivity final : public Activity {
ButtonNavigator buttonNavigator;
WifiSelectionState state = WifiSelectionState::SCANNING;
size_t selectedNetworkIndex = 0;
int selectedNetworkIndex = 0;
std::vector<WifiNetworkInfo> networks;
// Selected network for connection
@@ -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&&) {
@@ -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&&) {
+2 -21
View File
@@ -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&&) {
@@ -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&&) {
@@ -392,19 +392,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()) {
@@ -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() {
@@ -37,15 +37,8 @@ void LanguageSelectActivity::loop() {
}
// Handle navigation
buttonNavigator.onNextRelease([this] {
selectedIndex = ButtonNavigator::nextIndex(static_cast<int>(selectedIndex), totalItems);
requestUpdate();
});
buttonNavigator.onPreviousRelease([this] {
selectedIndex = ButtonNavigator::previousIndex(static_cast<int>(selectedIndex), totalItems);
requestUpdate();
});
buttonNavigator.onNextList(selectedIndex, totalItems, [this] { requestUpdate(); });
buttonNavigator.onPreviousList(selectedIndex, totalItems, [this] { requestUpdate(); });
}
void LanguageSelectActivity::handleSelection() {
@@ -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() {
@@ -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<int>(selectedIndex),
renderer, Rect{0, contentTop, pageWidth, contentHeight}, menuItems, selectedIndex,
[this, &fieldNames](int index) {
if (index < BASE_ITEMS) {
return std::string(I18N.get(fieldNames[index]));
@@ -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;
@@ -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() {
@@ -55,18 +55,8 @@ void WeatherSettingsActivity::loop() {
return;
}
buttonNavigator.onNext([this] {
if (searchResults.empty()) return;
selectedIndex = (selectedIndex + 1) % static_cast<int>(searchResults.size());
requestUpdate();
});
buttonNavigator.onPrevious([this] {
if (searchResults.empty()) return;
const int size = static_cast<int>(searchResults.size());
selectedIndex = (selectedIndex + size - 1) % size;
requestUpdate();
});
buttonNavigator.onNextList(selectedIndex, static_cast<int>(searchResults.size()), [this] { requestUpdate(); });
buttonNavigator.onPreviousList(selectedIndex, static_cast<int>(searchResults.size()), [this] { requestUpdate(); });
return;
}
+81
View File
@@ -224,3 +224,84 @@ 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.
selectedIndex = indexBeforePress;
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();
}
+19
View File
@@ -16,7 +16,19 @@ class ButtonNavigator final {
std::function<bool(int)> 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};
}