feat: unify navigation handling with system-wide continuous navigation (#600)
This PR unifies navigation handling & adds system-wide support for continuous navigation. ## Summary Holding down a navigation button now continuously advances through items until the button is released. This removes the need for repeated press-and-release actions and makes navigation faster and smoother, especially in long menus or documents. When page-based navigation is available, it will navigate through pages. If not, it will progress through menu items or similar list-based UI elements. Additionally, this PR fixes inconsistencies in wrap-around behavior and navigation index calculations. Places where the navigation system was updated: - Home Page - Settings Pages - My Library Page - WiFi Selection Page - OPDS Browser Page - Keyboard - File Transfer Page - XTC Chapter Selector Page - EPUB Chapter Selector Page I’ve tested this on the device as much as possible and tried to match the existing behavior. Please let me know if I missed anything. Thanks 🙏  --- Following the request from @osteotek and @daveallie for system-wide support, the old PR (#379) has been closed in favor of this consolidated, system-wide implementation. --- ### AI Usage Did you use AI tools to help write this code? _**PARTIALLY**_ --------- Co-authored-by: Dave Allie <dave@daveallie.com>
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int PAGE_ITEMS = 23;
|
constexpr int PAGE_ITEMS = 23;
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void OpdsBookBrowserActivity::taskTrampoline(void* param) {
|
void OpdsBookBrowserActivity::taskTrampoline(void* param) {
|
||||||
@@ -118,12 +117,6 @@ void OpdsBookBrowserActivity::loop() {
|
|||||||
|
|
||||||
// Handle browsing state
|
// Handle browsing state
|
||||||
if (state == BrowserState::BROWSING) {
|
if (state == BrowserState::BROWSING) {
|
||||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
||||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (!entries.empty()) {
|
if (!entries.empty()) {
|
||||||
const auto& entry = entries[selectorIndex];
|
const auto& entry = entries[selectorIndex];
|
||||||
@@ -135,20 +128,29 @@ void OpdsBookBrowserActivity::loop() {
|
|||||||
}
|
}
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (prevReleased && !entries.empty()) {
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + entries.size()) % entries.size();
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + entries.size() - 1) % entries.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle navigation
|
||||||
|
if (!entries.empty()) {
|
||||||
|
buttonNavigator.onNextRelease([this] {
|
||||||
|
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, entries.size());
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextReleased && !entries.empty()) {
|
});
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % entries.size();
|
buttonNavigator.onPreviousRelease([this] {
|
||||||
} else {
|
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, entries.size());
|
||||||
selectorIndex = (selectorIndex + 1) % entries.size();
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this] {
|
||||||
|
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, entries.size(), PAGE_ITEMS);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this] {
|
||||||
|
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, entries.size(), PAGE_ITEMS);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../ActivityWithSubactivity.h"
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for browsing and downloading books from an OPDS server.
|
* Activity for browsing and downloading books from an OPDS server.
|
||||||
@@ -37,6 +38,7 @@ class OpdsBookBrowserActivity final : public ActivityWithSubactivity {
|
|||||||
private:
|
private:
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|
||||||
BrowserState state = BrowserState::LOADING;
|
BrowserState state = BrowserState::LOADING;
|
||||||
|
|||||||
@@ -196,13 +196,18 @@ void HomeActivity::freeCoverBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HomeActivity::loop() {
|
void HomeActivity::loop() {
|
||||||
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
|
||||||
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
|
||||||
|
|
||||||
const int menuCount = getMenuItemCount();
|
const int menuCount = getMenuItemCount();
|
||||||
|
|
||||||
|
buttonNavigator.onNext([this, menuCount] {
|
||||||
|
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, menuCount);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPrevious([this, menuCount] {
|
||||||
|
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, menuCount);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
// Calculate dynamic indices based on which options are available
|
// Calculate dynamic indices based on which options are available
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
@@ -226,12 +231,6 @@ void HomeActivity::loop() {
|
|||||||
} else if (menuSelectedIndex == settingsIdx) {
|
} else if (menuSelectedIndex == settingsIdx) {
|
||||||
onSettingsOpen();
|
onSettingsOpen();
|
||||||
}
|
}
|
||||||
} else if (prevPressed) {
|
|
||||||
selectorIndex = (selectorIndex + menuCount - 1) % menuCount;
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (nextPressed) {
|
|
||||||
selectorIndex = (selectorIndex + 1) % menuCount;
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
#include "./MyLibraryActivity.h"
|
#include "./MyLibraryActivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
struct RecentBook;
|
struct RecentBook;
|
||||||
struct Rect;
|
struct Rect;
|
||||||
@@ -15,6 +16,7 @@ struct Rect;
|
|||||||
class HomeActivity final : public Activity {
|
class HomeActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
bool recentsLoading = false;
|
bool recentsLoading = false;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "util/StringUtils.h"
|
#include "util/StringUtils.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
constexpr unsigned long GO_HOME_MS = 1000;
|
constexpr unsigned long GO_HOME_MS = 1000;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -109,13 +108,6 @@ void MyLibraryActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Left) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Up);
|
|
||||||
;
|
|
||||||
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Right) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Down);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
|
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
@@ -157,21 +149,26 @@ void MyLibraryActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int listSize = static_cast<int>(files.size());
|
int listSize = static_cast<int>(files.size());
|
||||||
if (upReleased) {
|
|
||||||
if (skipPage) {
|
buttonNavigator.onNextRelease([this, listSize] {
|
||||||
selectorIndex = std::max(static_cast<int>((selectorIndex / pageItems - 1) * pageItems), 0);
|
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + listSize - 1) % listSize;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (downReleased) {
|
});
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = std::min(static_cast<int>((selectorIndex / pageItems + 1) * pageItems), listSize - 1);
|
buttonNavigator.onPreviousRelease([this, listSize] {
|
||||||
} else {
|
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
|
||||||
selectorIndex = (selectorIndex + 1) % listSize;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this, listSize, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this, listSize, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyLibraryActivity::displayTaskLoop() {
|
void MyLibraryActivity::displayTaskLoop() {
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
|
#include "RecentBooksStore.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class MyLibraryActivity final : public Activity {
|
class MyLibraryActivity final : public Activity {
|
||||||
private:
|
private:
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
|
|
||||||
size_t selectorIndex = 0;
|
size_t selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "util/StringUtils.h"
|
#include "util/StringUtils.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
constexpr unsigned long GO_HOME_MS = 1000;
|
constexpr unsigned long GO_HOME_MS = 1000;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -70,13 +69,6 @@ void RecentBooksActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RecentBooksActivity::loop() {
|
void RecentBooksActivity::loop() {
|
||||||
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Left) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Up);
|
|
||||||
;
|
|
||||||
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Right) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Down);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, true);
|
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, true);
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
@@ -92,21 +84,26 @@ void RecentBooksActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int listSize = static_cast<int>(recentBooks.size());
|
int listSize = static_cast<int>(recentBooks.size());
|
||||||
if (upReleased) {
|
|
||||||
if (skipPage) {
|
buttonNavigator.onNextRelease([this, listSize] {
|
||||||
selectorIndex = std::max(static_cast<int>((selectorIndex / pageItems - 1) * pageItems), 0);
|
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + listSize - 1) % listSize;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (downReleased) {
|
});
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = std::min(static_cast<int>((selectorIndex / pageItems + 1) * pageItems), listSize - 1);
|
buttonNavigator.onPreviousRelease([this, listSize] {
|
||||||
} else {
|
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
|
||||||
selectorIndex = (selectorIndex + 1) % listSize;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this, listSize, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this, listSize, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecentBooksActivity::displayTaskLoop() {
|
void RecentBooksActivity::displayTaskLoop() {
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
#include "RecentBooksStore.h"
|
#include "RecentBooksStore.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class RecentBooksActivity final : public Activity {
|
class RecentBooksActivity final : public Activity {
|
||||||
private:
|
private:
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
|
|
||||||
size_t selectorIndex = 0;
|
size_t selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|||||||
@@ -73,18 +73,15 @@ void NetworkModeSelectionActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation
|
// Handle navigation
|
||||||
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
buttonNavigator.onNext([this] {
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, MENU_ITEM_COUNT);
|
||||||
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
updateRequired = true;
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
});
|
||||||
|
|
||||||
if (prevPressed) {
|
buttonNavigator.onPrevious([this] {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT;
|
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, MENU_ITEM_COUNT);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextPressed) {
|
});
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEM_COUNT;
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkModeSelectionActivity::displayTaskLoop() {
|
void NetworkModeSelectionActivity::displayTaskLoop() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
// Enum for network mode selection
|
// Enum for network mode selection
|
||||||
enum class NetworkMode { JOIN_NETWORK, CONNECT_CALIBRE, CREATE_HOTSPOT };
|
enum class NetworkMode { JOIN_NETWORK, CONNECT_CALIBRE, CREATE_HOTSPOT };
|
||||||
@@ -22,6 +23,8 @@ enum class NetworkMode { JOIN_NETWORK, CONNECT_CALIBRE, CREATE_HOTSPOT };
|
|||||||
class NetworkModeSelectionActivity final : public Activity {
|
class NetworkModeSelectionActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void(NetworkMode)> onModeSelected;
|
const std::function<void(NetworkMode)> onModeSelected;
|
||||||
|
|||||||
@@ -420,20 +420,16 @@ void WifiSelectionActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle UP/DOWN navigation
|
// Handle navigation
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
buttonNavigator.onNext([this] {
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
selectedNetworkIndex = ButtonNavigator::nextIndex(selectedNetworkIndex, networks.size());
|
||||||
if (selectedNetworkIndex > 0) {
|
|
||||||
selectedNetworkIndex--;
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
buttonNavigator.onPrevious([this] {
|
||||||
if (!networks.empty() && selectedNetworkIndex < static_cast<int>(networks.size()) - 1) {
|
selectedNetworkIndex = ButtonNavigator::previousIndex(selectedNetworkIndex, networks.size());
|
||||||
selectedNetworkIndex++;
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
// Structure to hold WiFi network information
|
// Structure to hold WiFi network information
|
||||||
struct WifiNetworkInfo {
|
struct WifiNetworkInfo {
|
||||||
@@ -45,6 +46,7 @@ enum class WifiSelectionState {
|
|||||||
class WifiSelectionActivity final : public ActivityWithSubactivity {
|
class WifiSelectionActivity final : public ActivityWithSubactivity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
WifiSelectionState state = WifiSelectionState::SCANNING;
|
WifiSelectionState state = WifiSelectionState::SCANNING;
|
||||||
int selectedNetworkIndex = 0;
|
int selectedNetworkIndex = 0;
|
||||||
|
|||||||
@@ -6,11 +6,6 @@
|
|||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Time threshold for treating a long press as a page-up/page-down
|
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int EpubReaderChapterSelectionActivity::getTotalItems() const { return epub->getTocItemsCount(); }
|
int EpubReaderChapterSelectionActivity::getTotalItems() const { return epub->getTocItemsCount(); }
|
||||||
|
|
||||||
int EpubReaderChapterSelectionActivity::getPageItems() const {
|
int EpubReaderChapterSelectionActivity::getPageItems() const {
|
||||||
@@ -77,12 +72,6 @@ void EpubReaderChapterSelectionActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
||||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
const int totalItems = getTotalItems();
|
const int totalItems = getTotalItems();
|
||||||
|
|
||||||
@@ -95,21 +84,27 @@ void EpubReaderChapterSelectionActivity::loop() {
|
|||||||
}
|
}
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
} else if (prevReleased) {
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + totalItems) % totalItems;
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + totalItems - 1) % totalItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buttonNavigator.onNextRelease([this, totalItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextReleased) {
|
});
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % totalItems;
|
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||||
} else {
|
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
|
||||||
selectorIndex = (selectorIndex + 1) % totalItems;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
||||||
|
|||||||
@@ -7,12 +7,14 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../ActivityWithSubactivity.h"
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity {
|
class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity {
|
||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
std::string epubPath;
|
std::string epubPath;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
int currentSpineIndex = 0;
|
int currentSpineIndex = 0;
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int totalPagesInSpine = 0;
|
int totalPagesInSpine = 0;
|
||||||
|
|||||||
@@ -48,16 +48,19 @@ void EpubReaderMenuActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle navigation
|
||||||
|
buttonNavigator.onNext([this] {
|
||||||
|
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPrevious([this] {
|
||||||
|
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
// Use local variables for items we need to check after potential deletion
|
// Use local variables for items we need to check after potential deletion
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left)) {
|
|
||||||
selectedIndex = (selectedIndex + menuItems.size() - 1) % menuItems.size();
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
|
||||||
selectedIndex = (selectedIndex + 1) % menuItems.size();
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
||||||
const auto selectedAction = menuItems[selectedIndex].action;
|
const auto selectedAction = menuItems[selectedIndex].action;
|
||||||
if (selectedAction == MenuAction::ROTATE_SCREEN) {
|
if (selectedAction == MenuAction::ROTATE_SCREEN) {
|
||||||
// Cycle orientation preview locally; actual rotation happens on menu exit.
|
// Cycle orientation preview locally; actual rotation happens on menu exit.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../ActivityWithSubactivity.h"
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||||
public:
|
public:
|
||||||
@@ -48,6 +49,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
std::string title = "Reader Menu";
|
std::string title = "Reader Menu";
|
||||||
uint8_t pendingOrientation = 0;
|
uint8_t pendingOrientation = 0;
|
||||||
const std::vector<const char*> orientationLabels = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"};
|
const std::vector<const char*> orientationLabels = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"};
|
||||||
|
|||||||
@@ -79,25 +79,11 @@ void EpubReaderPercentSelectionActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Left)) {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] { adjustPercent(-kSmallStep); });
|
||||||
adjustPercent(-kSmallStep);
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] { adjustPercent(kSmallStep); });
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this] { adjustPercent(kLargeStep); });
|
||||||
adjustPercent(kSmallStep);
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this] { adjustPercent(-kLargeStep); });
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
|
|
||||||
adjustPercent(kLargeStep);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
|
|
||||||
adjustPercent(-kLargeStep);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderPercentSelectionActivity::renderScreen() {
|
void EpubReaderPercentSelectionActivity::renderScreen() {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity {
|
class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity {
|
||||||
public:
|
public:
|
||||||
@@ -31,6 +32,7 @@ class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity
|
|||||||
// FreeRTOS task and mutex for rendering.
|
// FreeRTOS task and mutex for rendering.
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
|
|
||||||
// Callback invoked when the user confirms a percent.
|
// Callback invoked when the user confirms a percent.
|
||||||
const std::function<void(int)> onSelect;
|
const std::function<void(int)> onSelect;
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int XtcReaderChapterSelectionActivity::getPageItems() const {
|
int XtcReaderChapterSelectionActivity::getPageItems() const {
|
||||||
constexpr int lineHeight = 30;
|
constexpr int lineHeight = 30;
|
||||||
|
|
||||||
@@ -78,13 +74,8 @@ void XtcReaderChapterSelectionActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::loop() {
|
void XtcReaderChapterSelectionActivity::loop() {
|
||||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
||||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
||||||
|
|
||||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
|
const int totalItems = static_cast<int>(xtc->getChapters().size());
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
const auto& chapters = xtc->getChapters();
|
const auto& chapters = xtc->getChapters();
|
||||||
@@ -93,29 +84,27 @@ void XtcReaderChapterSelectionActivity::loop() {
|
|||||||
}
|
}
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
} else if (prevReleased) {
|
|
||||||
const int total = static_cast<int>(xtc->getChapters().size());
|
|
||||||
if (total == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + total) % total;
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + total - 1) % total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buttonNavigator.onNextRelease([this, totalItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextReleased) {
|
});
|
||||||
const int total = static_cast<int>(xtc->getChapters().size());
|
|
||||||
if (total == 0) {
|
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||||
return;
|
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
|
||||||
}
|
|
||||||
if (skipPage) {
|
|
||||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % total;
|
|
||||||
} else {
|
|
||||||
selectorIndex = (selectorIndex + 1) % total;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||||
|
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::displayTaskLoop() {
|
void XtcReaderChapterSelectionActivity::displayTaskLoop() {
|
||||||
|
|||||||
@@ -7,11 +7,13 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class XtcReaderChapterSelectionActivity final : public Activity {
|
class XtcReaderChapterSelectionActivity final : public Activity {
|
||||||
std::shared_ptr<Xtc> xtc;
|
std::shared_ptr<Xtc> xtc;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
uint32_t currentPage = 0;
|
uint32_t currentPage = 0;
|
||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|||||||
@@ -63,15 +63,16 @@ void CalibreSettingsActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
// Handle navigation
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
buttonNavigator.onNext([this] {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPrevious([this] {
|
||||||
|
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreSettingsActivity::handleSelection() {
|
void CalibreSettingsActivity::handleSelection() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submenu for OPDS Browser settings.
|
* Submenu for OPDS Browser settings.
|
||||||
@@ -24,6 +25,7 @@ class CalibreSettingsActivity final : public ActivityWithSubactivity {
|
|||||||
private:
|
private:
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
|
|||||||
@@ -64,15 +64,16 @@ void KOReaderSettingsActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
// Handle navigation
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
buttonNavigator.onNext([this] {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPrevious([this] {
|
||||||
|
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSettingsActivity::handleSelection() {
|
void KOReaderSettingsActivity::handleSelection() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submenu for KOReader Sync settings.
|
* Submenu for KOReader Sync settings.
|
||||||
@@ -24,6 +25,7 @@ class KOReaderSettingsActivity final : public ActivityWithSubactivity {
|
|||||||
private:
|
private:
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
|
|||||||
@@ -16,10 +16,6 @@
|
|||||||
|
|
||||||
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int changeTabsMs = 700;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<SettingsActivity*>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
@@ -116,28 +112,28 @@ void SettingsActivity::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 changeTab = mappedInput.getHeldTime() > changeTabsMs;
|
|
||||||
|
|
||||||
// Handle navigation
|
// Handle navigation
|
||||||
if (upReleased && changeTab) {
|
buttonNavigator.onNextRelease([this] {
|
||||||
|
selectedSettingIndex = ButtonNavigator::nextIndex(selectedSettingIndex, settingsCount + 1);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousRelease([this] {
|
||||||
|
selectedSettingIndex = ButtonNavigator::previousIndex(selectedSettingIndex, settingsCount + 1);
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onNextContinuous([this, &hasChangedCategory] {
|
||||||
hasChangedCategory = true;
|
hasChangedCategory = true;
|
||||||
selectedCategoryIndex = (selectedCategoryIndex > 0) ? (selectedCategoryIndex - 1) : (categoryCount - 1);
|
selectedCategoryIndex = ButtonNavigator::nextIndex(selectedCategoryIndex, categoryCount);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (downReleased && changeTab) {
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPreviousContinuous([this, &hasChangedCategory] {
|
||||||
hasChangedCategory = true;
|
hasChangedCategory = true;
|
||||||
selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1) ? (selectedCategoryIndex + 1) : 0;
|
selectedCategoryIndex = ButtonNavigator::previousIndex(selectedCategoryIndex, categoryCount);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (upReleased || leftReleased) {
|
});
|
||||||
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount);
|
|
||||||
updateRequired = true;
|
|
||||||
} else if (rightReleased || downReleased) {
|
|
||||||
selectedSettingIndex = (selectedSettingIndex < settingsCount) ? (selectedSettingIndex + 1) : 0;
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChangedCategory) {
|
if (hasChangedCategory) {
|
||||||
selectedSettingIndex = (selectedSettingIndex == 0) ? 0 : 1;
|
selectedSettingIndex = (selectedSettingIndex == 0) ? 0 : 1;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
class CrossPointSettings;
|
class CrossPointSettings;
|
||||||
|
|
||||||
@@ -124,6 +125,7 @@ struct SettingInfo {
|
|||||||
class SettingsActivity final : public ActivityWithSubactivity {
|
class SettingsActivity final : public ActivityWithSubactivity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
int selectedCategoryIndex = 0; // Currently selected category
|
int selectedCategoryIndex = 0; // Currently selected category
|
||||||
int selectedSettingIndex = 0;
|
int selectedSettingIndex = 0;
|
||||||
|
|||||||
@@ -142,37 +142,24 @@ void KeyboardEntryActivity::handleKeyPress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KeyboardEntryActivity::loop() {
|
void KeyboardEntryActivity::loop() {
|
||||||
// Navigation
|
// Handle navigation
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up)) {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Up}, [this] {
|
||||||
if (selectedRow > 0) {
|
selectedRow = ButtonNavigator::previousIndex(selectedRow, NUM_ROWS);
|
||||||
selectedRow--;
|
|
||||||
// Clamp column to valid range for new row
|
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
||||||
} else {
|
|
||||||
// Wrap to bottom row
|
|
||||||
selectedRow = NUM_ROWS - 1;
|
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
|
||||||
if (selectedRow < NUM_ROWS - 1) {
|
|
||||||
selectedRow++;
|
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
} else {
|
|
||||||
// Wrap to top row
|
|
||||||
selectedRow = 0;
|
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this] {
|
||||||
|
selectedRow = ButtonNavigator::nextIndex(selectedRow, NUM_ROWS);
|
||||||
|
|
||||||
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Left}, [this] {
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
|
||||||
// Special bottom row case
|
// Special bottom row case
|
||||||
@@ -191,20 +178,14 @@ void KeyboardEntryActivity::loop() {
|
|||||||
// At done button, move to backspace
|
// At done button, move to backspace
|
||||||
selectedCol = BACKSPACE_COL;
|
selectedCol = BACKSPACE_COL;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedCol > 0) {
|
|
||||||
selectedCol--;
|
|
||||||
} else {
|
} else {
|
||||||
// Wrap to end of current row
|
selectedCol = ButtonNavigator::previousIndex(selectedCol, maxCol + 1);
|
||||||
selectedCol = maxCol;
|
|
||||||
}
|
|
||||||
updateRequired = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
updateRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Right}, [this] {
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
|
||||||
// Special bottom row case
|
// Special bottom row case
|
||||||
@@ -223,18 +204,11 @@ void KeyboardEntryActivity::loop() {
|
|||||||
// At done button, wrap to beginning of row
|
// At done button, wrap to beginning of row
|
||||||
selectedCol = SHIFT_COL;
|
selectedCol = SHIFT_COL;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedCol < maxCol) {
|
|
||||||
selectedCol++;
|
|
||||||
} else {
|
} else {
|
||||||
// Wrap to beginning of current row
|
selectedCol = ButtonNavigator::nextIndex(selectedCol, maxCol + 1);
|
||||||
selectedCol = 0;
|
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../Activity.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable keyboard entry activity for text input.
|
* Reusable keyboard entry activity for text input.
|
||||||
@@ -65,6 +66,7 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
bool isPassword;
|
bool isPassword;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
ButtonNavigator buttonNavigator;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
|
||||||
// Keyboard state
|
// Keyboard state
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "activities/util/FullScreenMessageActivity.h"
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
#include "util/ButtonNavigator.h"
|
||||||
|
|
||||||
HalDisplay display;
|
HalDisplay display;
|
||||||
HalGPIO gpio;
|
HalGPIO gpio;
|
||||||
@@ -304,6 +305,7 @@ void setup() {
|
|||||||
SETTINGS.loadFromFile();
|
SETTINGS.loadFromFile();
|
||||||
KOREADER_STORE.loadFromFile();
|
KOREADER_STORE.loadFromFile();
|
||||||
UITheme::getInstance().reload();
|
UITheme::getInstance().reload();
|
||||||
|
ButtonNavigator::setMappedInputManager(mappedInputManager);
|
||||||
|
|
||||||
switch (gpio.getWakeupReason()) {
|
switch (gpio.getWakeupReason()) {
|
||||||
case HalGPIO::WakeupReason::PowerButton:
|
case HalGPIO::WakeupReason::PowerButton:
|
||||||
|
|||||||
124
src/util/ButtonNavigator.cpp
Normal file
124
src/util/ButtonNavigator.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#include "ButtonNavigator.h"
|
||||||
|
|
||||||
|
const MappedInputManager* ButtonNavigator::mappedInput = nullptr;
|
||||||
|
|
||||||
|
void ButtonNavigator::onNext(const Callback& callback) {
|
||||||
|
onNextPress(callback);
|
||||||
|
onNextContinuous(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonNavigator::onPrevious(const Callback& callback) {
|
||||||
|
onPreviousPress(callback);
|
||||||
|
onPreviousContinuous(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonNavigator::onPressAndContinuous(const Buttons& buttons, const Callback& callback) {
|
||||||
|
onPress(buttons, callback);
|
||||||
|
onContinuous(buttons, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonNavigator::onNextPress(const Callback& callback) { onPress(getNextButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onPreviousPress(const Callback& callback) { onPress(getPreviousButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onNextRelease(const Callback& callback) { onRelease(getNextButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onPreviousRelease(const Callback& callback) { onRelease(getPreviousButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onNextContinuous(const Callback& callback) { onContinuous(getNextButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onPreviousContinuous(const Callback& callback) { onContinuous(getPreviousButtons(), callback); }
|
||||||
|
|
||||||
|
void ButtonNavigator::onPress(const Buttons& buttons, const Callback& callback) {
|
||||||
|
const bool wasPressed = std::any_of(buttons.begin(), buttons.end(), [](const MappedInputManager::Button button) {
|
||||||
|
return mappedInput != nullptr && mappedInput->wasPressed(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wasPressed) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonNavigator::onRelease(const Buttons& buttons, const Callback& callback) {
|
||||||
|
const bool wasReleased = std::any_of(buttons.begin(), buttons.end(), [](const MappedInputManager::Button button) {
|
||||||
|
return mappedInput != nullptr && mappedInput->wasReleased(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (wasReleased) {
|
||||||
|
if (lastContinuousNavTime == 0) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastContinuousNavTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonNavigator::onContinuous(const Buttons& buttons, const Callback& callback) {
|
||||||
|
const bool isPressed = std::any_of(buttons.begin(), buttons.end(), [this](const MappedInputManager::Button button) {
|
||||||
|
return mappedInput != nullptr && mappedInput->isPressed(button) && shouldNavigateContinuously();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPressed) {
|
||||||
|
callback();
|
||||||
|
lastContinuousNavTime = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ButtonNavigator::shouldNavigateContinuously() const {
|
||||||
|
if (!mappedInput) return false;
|
||||||
|
|
||||||
|
const bool buttonHeldLongEnough = mappedInput->getHeldTime() > continuousStartMs;
|
||||||
|
const bool navigationIntervalElapsed = (millis() - lastContinuousNavTime) > continuousIntervalMs;
|
||||||
|
|
||||||
|
return buttonHeldLongEnough && navigationIntervalElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ButtonNavigator::nextIndex(const int currentIndex, const int totalItems) {
|
||||||
|
if (totalItems <= 0) return 0;
|
||||||
|
|
||||||
|
// Calculate the next index with wrap-around
|
||||||
|
return (currentIndex + 1) % totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ButtonNavigator::previousIndex(const int currentIndex, const int totalItems) {
|
||||||
|
if (totalItems <= 0) return 0;
|
||||||
|
|
||||||
|
// Calculate the previous index with wrap-around
|
||||||
|
return (currentIndex + totalItems - 1) % totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ButtonNavigator::nextPageIndex(const int currentIndex, const int totalItems, const int itemsPerPage) {
|
||||||
|
if (totalItems <= 0 || itemsPerPage <= 0) return 0;
|
||||||
|
|
||||||
|
// When items fit on one page, use index navigation instead
|
||||||
|
if (totalItems <= itemsPerPage) {
|
||||||
|
return nextIndex(currentIndex, totalItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lastPageIndex = (totalItems - 1) / itemsPerPage;
|
||||||
|
const int currentPageIndex = currentIndex / itemsPerPage;
|
||||||
|
|
||||||
|
if (currentPageIndex < lastPageIndex) {
|
||||||
|
return (currentPageIndex + 1) * itemsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ButtonNavigator::previousPageIndex(const int currentIndex, const int totalItems, const int itemsPerPage) {
|
||||||
|
if (totalItems <= 0 || itemsPerPage <= 0) return 0;
|
||||||
|
|
||||||
|
// When items fit on one page, use index navigation instead
|
||||||
|
if (totalItems <= itemsPerPage) {
|
||||||
|
return previousIndex(currentIndex, totalItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lastPageIndex = (totalItems - 1) / itemsPerPage;
|
||||||
|
const int currentPageIndex = currentIndex / itemsPerPage;
|
||||||
|
|
||||||
|
if (currentPageIndex > 0) {
|
||||||
|
return (currentPageIndex - 1) * itemsPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastPageIndex * itemsPerPage;
|
||||||
|
}
|
||||||
53
src/util/ButtonNavigator.h
Normal file
53
src/util/ButtonNavigator.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
|
||||||
|
class ButtonNavigator final {
|
||||||
|
using Callback = std::function<void()>;
|
||||||
|
using Buttons = std::vector<MappedInputManager::Button>;
|
||||||
|
|
||||||
|
const uint16_t continuousStartMs;
|
||||||
|
const uint16_t continuousIntervalMs;
|
||||||
|
uint32_t lastContinuousNavTime = 0;
|
||||||
|
static const MappedInputManager* mappedInput;
|
||||||
|
|
||||||
|
[[nodiscard]] bool shouldNavigateContinuously() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ButtonNavigator(const uint16_t continuousIntervalMs = 500, const uint16_t continuousStartMs = 500)
|
||||||
|
: continuousStartMs(continuousStartMs), continuousIntervalMs(continuousIntervalMs) {}
|
||||||
|
|
||||||
|
static void setMappedInputManager(const MappedInputManager& mappedInputManager) { mappedInput = &mappedInputManager; }
|
||||||
|
|
||||||
|
void onNext(const Callback& callback);
|
||||||
|
void onPrevious(const Callback& callback);
|
||||||
|
void onPressAndContinuous(const Buttons& buttons, const Callback& callback);
|
||||||
|
|
||||||
|
void onNextPress(const Callback& callback);
|
||||||
|
void onPreviousPress(const Callback& callback);
|
||||||
|
void onPress(const Buttons& buttons, const Callback& callback);
|
||||||
|
|
||||||
|
void onNextRelease(const Callback& callback);
|
||||||
|
void onPreviousRelease(const Callback& callback);
|
||||||
|
void onRelease(const Buttons& buttons, const Callback& callback);
|
||||||
|
|
||||||
|
void onNextContinuous(const Callback& callback);
|
||||||
|
void onPreviousContinuous(const Callback& callback);
|
||||||
|
void onContinuous(const Buttons& buttons, const Callback& callback);
|
||||||
|
|
||||||
|
[[nodiscard]] static int nextIndex(int currentIndex, int totalItems);
|
||||||
|
[[nodiscard]] static int previousIndex(int currentIndex, int totalItems);
|
||||||
|
|
||||||
|
[[nodiscard]] static int nextPageIndex(int currentIndex, int totalItems, int itemsPerPage);
|
||||||
|
[[nodiscard]] static int previousPageIndex(int currentIndex, int totalItems, int itemsPerPage);
|
||||||
|
|
||||||
|
[[nodiscard]] static Buttons getNextButtons() {
|
||||||
|
return {MappedInputManager::Button::Down, MappedInputManager::Button::Right};
|
||||||
|
}
|
||||||
|
[[nodiscard]] static Buttons getPreviousButtons() {
|
||||||
|
return {MappedInputManager::Button::Up, MappedInputManager::Button::Left};
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user