Add icon rendering and list scroll gesture support

Implement orientation-aware icon rendering using freeink::Icon format that applies rotation transforms per-pixel. Add wasListScroll helper for touch-based list navigation with swipe gestures. Switch vertical centering from x-height to cap-height for better visual alignment with capital-led UI labels.
This commit is contained in:
Justin Mitchell
2026-06-15 23:37:49 -04:00
parent e103cdfd11
commit 4a5b45409f
20 changed files with 339 additions and 96 deletions
@@ -206,6 +206,14 @@ void FileBrowserActivity::loop() {
const int pathReserved = renderer.getLineHeight(SMALL_FONT_ID) + UITheme::getInstance().getMetrics().verticalSpacing;
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved);
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
int scrollIdx = static_cast<int>(selectorIndex);
if (mappedInput.wasListScroll(scrollIdx, static_cast<int>(files.size()), pageItems)) {
selectorIndex = scrollIdx;
requestUpdate();
return;
}
// Touch-down moves the selector to the pressed entry (shows selected state); release
// opens it below.
int downId = -1;
@@ -44,6 +44,14 @@ void RecentBooksActivity::onExit() {
void RecentBooksActivity::loop() {
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, true);
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
int scrollIdx = static_cast<int>(selectorIndex);
if (mappedInput.wasListScroll(scrollIdx, static_cast<int>(recentBooks.size()), pageItems)) {
selectorIndex = scrollIdx;
requestUpdate();
return;
}
// After a long-press has fired, swallow input until Confirm is physically released
// (so the release doesn't also open the book; re-arm only once the button is up).
if (longPressFired) {
@@ -409,6 +409,14 @@ void WifiSelectionActivity::loop() {
// Handle network list state
if (state == WifiSelectionState::NETWORK_LIST) {
// Vertical swipe page-scrolls the network list (touch nav without the side buttons).
int scrollIdx = static_cast<int>(selectedNetworkIndex);
if (mappedInput.wasListScroll(scrollIdx, static_cast<int>(networks.size()),
UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false))) {
selectedNetworkIndex = scrollIdx;
requestUpdate();
return;
}
// Touch: down-select highlights the pressed network, tap selects it (like Confirm).
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(networks.size())) {
@@ -104,6 +104,12 @@ void EpubReaderBookmarksActivity::loop() {
}
if (confirmingDelete < DELETE_MODE_DISPLAY) {
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
const int pageItems = GUI.getListPageItems(getListHeight(renderer), true);
if (mappedInput.wasListScroll(selectorIndex, static_cast<int>(bookmarks.size()), pageItems)) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(bookmarks.size())) {
selectorIndex = downId;
@@ -31,6 +31,12 @@ void EpubReaderChapterSelectionActivity::loop() {
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
const int totalItems = getTotalItems();
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectorIndex, totalItems, pageItems)) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < totalItems) {
selectorIndex = downId;
@@ -57,6 +57,13 @@ void EpubReaderMenuActivity::loop() {
requestUpdate();
});
// Vertical swipe page-scrolls the menu (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectedIndex, static_cast<int>(menuItems.size()),
UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false))) {
requestUpdate();
return;
}
// Touch-down moves the selection to the pressed item (shows selected state), like
// moving with Up/Down; release activates it below.
int downId = -1;
@@ -431,6 +431,11 @@ void FontDownloadActivity::loop() {
const int listSize = listItemCount();
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false);
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectedIndex_, listSize, pageItems)) {
requestUpdate();
return;
}
buttonNavigator_.onNextRelease([this, listSize] {
selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, listSize);
@@ -54,6 +54,14 @@ void FontSelectionActivity::loop() {
return;
}
const int listSize = static_cast<int>(fonts_.size());
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false);
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectedIndex_, listSize, pageItems)) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex_ = downId;
@@ -72,9 +80,6 @@ void FontSelectionActivity::loop() {
return;
}
const int listSize = static_cast<int>(fonts_.size());
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false);
buttonNavigator_.onNextRelease([this, listSize] {
selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, listSize);
requestUpdate();
@@ -32,6 +32,13 @@ void LanguageSelectActivity::loop() {
return;
}
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false);
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectedIndex, totalItems, pageItems)) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex = downId;
@@ -50,8 +57,6 @@ void LanguageSelectActivity::loop() {
return;
}
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false);
// Handle navigation
buttonNavigator.onNextRelease([this] {
selectedIndex = ButtonNavigator::nextIndex(static_cast<int>(selectedIndex), totalItems);
@@ -41,6 +41,13 @@ void OpdsServerListActivity::loop() {
return;
}
// Vertical swipe page-scrolls the list (touch nav without the side buttons).
if (mappedInput.wasListScroll(selectedIndex, getItemCount(),
UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false))) {
requestUpdate();
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex = downId;
@@ -130,6 +130,17 @@ void SettingsActivity::loop() {
selectedSettingIndex = 0;
hasChangedCategory = true;
requestUpdate();
} else if (swipe == MappedInputManager::SwipeDir::Up || swipe == MappedInputManager::SwipeDir::Down) {
// Vertical swipe scrolls the settings list a page at a time (touch navigation
// without the side buttons). Swipe up moves down the list, and vice versa.
const int page = std::max(
1, UITheme::getNumberOfItemsPerPage(renderer, true, true, BaseTheme::showButtonHints(), false));
if (swipe == MappedInputManager::SwipeDir::Up) {
selectedSettingIndex = std::min(selectedSettingIndex + page, settingsCount);
} else {
selectedSettingIndex = std::max(selectedSettingIndex - page, 0);
}
requestUpdate();
}
// Tap a settings row to select + activate it. Row 0 is the tab bar, so the list