Files
crosspoint-reader-mod/src/activities/home/MyLibraryActivity.cpp

232 lines
7.0 KiB
C++
Raw Normal View History

My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
#include "MyLibraryActivity.h"
#include <GfxRenderer.h>
#include <HalStorage.h>
feat: User-Interface I18n System (#728) **What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com>
2026-02-16 15:28:42 +02:00
#include <I18n.h>
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
#include <algorithm>
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
#include "MappedInputManager.h"
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
#include "components/UITheme.h"
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
#include "fontIds.h"
#include "util/StringUtils.h"
namespace {
constexpr unsigned long GO_HOME_MS = 1000;
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
} // namespace
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
void sortFileList(std::vector<std::string>& strs) {
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
// Directories first
bool isDir1 = str1.back() == '/';
bool isDir2 = str2.back() == '/';
if (isDir1 != isDir2) return isDir1;
// Start naive natural sort
const char* s1 = str1.c_str();
const char* s2 = str2.c_str();
// Iterate while both strings have characters
while (*s1 && *s2) {
// Check if both are at the start of a number
if (isdigit(*s1) && isdigit(*s2)) {
// Skip leading zeros and track them
const char* start1 = s1;
const char* start2 = s2;
while (*s1 == '0') s1++;
while (*s2 == '0') s2++;
// Count digits to compare lengths first
int len1 = 0, len2 = 0;
while (isdigit(s1[len1])) len1++;
while (isdigit(s2[len2])) len2++;
// Different length so return smaller integer value
if (len1 != len2) return len1 < len2;
// Same length so compare digit by digit
for (int i = 0; i < len1; i++) {
if (s1[i] != s2[i]) return s1[i] < s2[i];
}
// Numbers equal so advance pointers
s1 += len1;
s2 += len2;
} else {
// Regular case-insensitive character comparison
char c1 = tolower(*s1);
char c2 = tolower(*s2);
if (c1 != c2) return c1 < c2;
s1++;
s2++;
}
}
// One string is prefix of other
return *s1 == '\0' && *s2 != '\0';
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
});
}
void MyLibraryActivity::loadFiles() {
files.clear();
auto root = Storage.open(basepath.c_str());
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
if (!root || !root.isDirectory()) {
if (root) root.close();
return;
}
root.rewindDirectory();
char name[500];
for (auto file = root.openNextFile(); file; file = root.openNextFile()) {
file.getName(name, sizeof(name));
if (name[0] == '.' || strcmp(name, "System Volume Information") == 0) {
file.close();
continue;
}
if (file.isDirectory()) {
files.emplace_back(std::string(name) + "/");
} else {
auto filename = std::string(name);
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") ||
StringUtils::checkFileExtension(filename, ".md")) {
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
files.emplace_back(filename);
}
}
file.close();
}
root.close();
sortFileList(files);
}
void MyLibraryActivity::onEnter() {
Activity::onEnter();
loadFiles();
selectorIndex = 0;
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
}
void MyLibraryActivity::onExit() {
Activity::onExit();
files.clear();
}
void MyLibraryActivity::loop() {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
// Long press BACK (1s+) goes to root folder
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS &&
basepath != "/") {
basepath = "/";
loadFiles();
selectorIndex = 0;
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
return;
}
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
if (files.empty()) {
return;
}
if (basepath.back() != '/') basepath += "/";
if (files[selectorIndex].back() == '/') {
basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1);
loadFiles();
selectorIndex = 0;
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
} else {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
onSelectBook(basepath + files[selectorIndex]);
return;
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
// Short press: go up one directory, or go home if at root
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
if (mappedInput.getHeldTime() < GO_HOME_MS) {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
if (basepath != "/") {
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
const std::string oldPath = basepath;
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
if (basepath.empty()) basepath = "/";
loadFiles();
const auto pos = oldPath.find_last_of('/');
const std::string dirName = oldPath.substr(pos + 1) + "/";
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
selectorIndex = findEntry(dirName);
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
} else {
onGoHome();
}
}
}
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
int listSize = static_cast<int>(files.size());
buttonNavigator.onNextRelease([this, listSize] {
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
});
buttonNavigator.onPreviousRelease([this, listSize] {
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
});
buttonNavigator.onNextContinuous([this, listSize, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
});
buttonNavigator.onPreviousContinuous([this, listSize, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
requestUpdate();
});
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
}
feat: Lyra Icons (#725) /!\ This PR depends on https://github.com/crosspoint-reader/crosspoint-reader/pull/732 being merged first Also requires the https://github.com/open-x4-epaper/community-sdk/pull/18 PR Lyra theme icons on the home menu, in the file browser and on empty book covers ![IMG_8023 Medium](https://github.com/user-attachments/assets/ba7c1407-94d2-4353-80ff-d5b800c6ac5b) ![IMG_8024 Medium](https://github.com/user-attachments/assets/edb59e13-b1c9-4c86-bef3-c61cc8134e64) ![IMG_7958 Medium](https://github.com/user-attachments/assets/d3079ce1-95f0-43f4-bbc7-1f747cc70203) ![IMG_8033 Medium](https://github.com/user-attachments/assets/f3e2e03b-0fa8-47b7-8717-c0b71361b7a8) - Added a function to the open-x4-sdk renderer to draw transparent images - Added a scripts/convert_icon.py script to convert svg/png icons into a C array that can be directly imported into the project. Usage: ```bash python ./scripts/convert_icon.py 'path/to/icon.png' cover 32 32 ``` This will create a components/icons/cover.h file with a C array called CoverIcon, of size 32x32px. Lyra uses icons from https://lucide.dev/icons with a stroke width of 2px, that can be downloaded with any desired size on the site. > The file browser is noticeably slower with the addition of icons, and using an image buffer like on the home page doesn't help very much. Any suggestions to optimize this are welcome. --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ The icon conversion python script was generated by Copilot as I am not a python dev. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-19 17:38:09 +07:00
std::string getFileName(std::string filename) {
if (filename.back() == '/') {
return filename.substr(0, filename.length() - 1);
}
const auto pos = filename.rfind('.');
return filename.substr(0, pos);
}
refactor: move render() to Activity super class, use freeRTOS notification (#774) Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
2026-02-16 11:11:15 +01:00
void MyLibraryActivity::render(Activity::RenderLock&&) {
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
renderer.clearScreen();
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
auto metrics = UITheme::getInstance().getMetrics();
2026-02-16 23:23:13 +02:00
std::string folderName = (basepath == "/") ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1);
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName.c_str());
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing;
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
if (files.empty()) {
feat: User-Interface I18n System (#728) **What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com>
2026-02-16 15:28:42 +02:00
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_BOOKS_FOUND));
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
} else {
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex,
feat: Lyra Icons (#725) /!\ This PR depends on https://github.com/crosspoint-reader/crosspoint-reader/pull/732 being merged first Also requires the https://github.com/open-x4-epaper/community-sdk/pull/18 PR Lyra theme icons on the home menu, in the file browser and on empty book covers ![IMG_8023 Medium](https://github.com/user-attachments/assets/ba7c1407-94d2-4353-80ff-d5b800c6ac5b) ![IMG_8024 Medium](https://github.com/user-attachments/assets/edb59e13-b1c9-4c86-bef3-c61cc8134e64) ![IMG_7958 Medium](https://github.com/user-attachments/assets/d3079ce1-95f0-43f4-bbc7-1f747cc70203) ![IMG_8033 Medium](https://github.com/user-attachments/assets/f3e2e03b-0fa8-47b7-8717-c0b71361b7a8) - Added a function to the open-x4-sdk renderer to draw transparent images - Added a scripts/convert_icon.py script to convert svg/png icons into a C array that can be directly imported into the project. Usage: ```bash python ./scripts/convert_icon.py 'path/to/icon.png' cover 32 32 ``` This will create a components/icons/cover.h file with a C array called CoverIcon, of size 32x32px. Lyra uses icons from https://lucide.dev/icons with a stroke width of 2px, that can be downloaded with any desired size on the site. > The file browser is noticeably slower with the addition of icons, and using an image buffer like on the home page doesn't help very much. Any suggestions to optimize this are welcome. --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ The icon conversion python script was generated by Copilot as I am not a python dev. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-19 17:38:09 +07:00
[this](int index) { return getFileName(files[index]); }, nullptr,
[this](int index) { return UITheme::getFileIcon(files[index]); });
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
}
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
// Help text
feat: User-Interface I18n System (#728) **What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com>
2026-02-16 15:28:42 +02:00
const auto labels = mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), tr(STR_OPEN), tr(STR_DIR_UP),
tr(STR_DIR_DOWN));
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
My Library: Tab bar w/ Recent Books + File Browser (#250) # Summary This PR introduces a reusable Tab Bar component and combines the Recent Books and File Browser into a unified tabbed page called "My Library" accessible from the Home screen. ## Features ### New Tab Bar Component A flexible, reusable tab bar component added to `ScreenComponents` that can be used throughout the application. ### New Scroll Indicator Component A page position indicator for lists that span multiple pages. **Features:** - Up/down arrow indicators - Current page fraction display (e.g., "1/3") - Only renders when content spans multiple pages ### My Library Activity A new unified view combining Recent Books and File Browser into a single tabbed page. **Tabs:** - **Recent** - Shows recently opened books - **Files** - Browse SD card directory structure **Navigation:** - Up/Down or Left/Right: Navigate through list items - Left/Right (when first item selected): Switch between tabs - Confirm: Open selected book or enter directory - Back: Go up directory (Files tab) or return home - Long press Back: Jump to root directory (Files tab) **UI Elements:** - Tab bar with selection indicator - Scroll/page indicator on right side - Side button hints (up/down arrows) - Dynamic bottom button labels ("BACK" in subdirectories, "HOME" at root) ## Tab Bar Usage The tab bar component is designed to be reusable across different activities. Here's how to use it: ### Basic Example ```cpp #include "ScreenComponents.h" void MyActivity::render() const { renderer.clearScreen(); // Define tabs with labels and selection state std::vector<TabInfo> tabs = { {"Tab One", currentTab == 0}, // Selected when currentTab is 0 {"Tab Two", currentTab == 1}, // Selected when currentTab is 1 {"Tab Three", currentTab == 2} // Selected when currentTab is 2 }; // Draw tab bar at Y position 15, returns height of the tab bar int tabBarHeight = ScreenComponents::drawTabBar(renderer, 15, tabs); // Position your content below the tab bar int contentStartY = 15 + tabBarHeight + 10; // Add some padding // Draw content based on selected tab if (currentTab == 0) { renderTabOneContent(contentStartY); } else if (currentTab == 1) { renderTabTwoContent(contentStartY); } else { renderTabThreeContent(contentStartY); } renderer.displayBuffer(); } ``` Video Demo: https://share.cleanshot.com/P6NBncFS <img width="250" src="https://github.com/user-attachments/assets/07de4418-968e-4a88-9b42-ac5f53d8a832" /> <img width="250" src="https://github.com/user-attachments/assets/e40201ed-dcc8-4568-b008-cd2bf13ebb2a" /> <img width="250" src="https://github.com/user-attachments/assets/73db269f-e629-4696-b8ca-0b8443451a05" /> --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-21 05:38:38 -06:00
renderer.displayBuffer();
}
feat: UI themes, Lyra (#528) ## Summary ### What is the goal of this PR? - Visual UI overhaul - UI theme selection ### What changes are included? - Added a setting "UI Theme": Classic, Lyra - The classic theme is the current Crosspoint theme - The Lyra theme implements these mockups: https://www.figma.com/design/UhxoV4DgUnfrDQgMPPTXog/Lyra-Theme?node-id=2003-7596&t=4CSOZqf0n9uQMxDt-0 by Discord users yagofarias, ruby and gan_shu - New functions in GFXRenderer to render rounded rectangles, greyscale fills (using dithering) and thick lines - Basic UI components are factored into BaseTheme methods which can be overridden by each additional theme. Methods that are not overridden will fallback to BaseTheme behavior. This means any new features/components in CrossPoint only need to be developed for the "Classic" BaseTheme. - Additional themes can easily be developed by the community using this foundation ![IMG_7649 Medium](https://github.com/user-attachments/assets/b516f5a9-2636-4565-acff-91a25b93b39b) ![IMG_7746 Medium](https://github.com/user-attachments/assets/def41810-ab6e-4952-b40f-b9ce7d62bea8) ![IMG_7651 Medium](https://github.com/user-attachments/assets/518a9a6d-107a-4be3-9533-43a2b64b944b) ## Additional Context - Only the Home, Library and main Settings screens have been implemented so far, this will be extended to the transfer screens and chapter selection screen later on, but we need to get the ball rolling somehow :) - Loading extra covers on the home screen in the Lyra theme takes a little more time (about 2 seconds), I added a loading bar popup (reusing the Indexing progress bar from the reader view, factored into a neat UI component) but the popup adds ~400ms to the loading time. - ~~Home screen thumbnails will need to be generated separately for each theme, because they are displayed in different sizes. Because we're using dithering, displaying a thumb with the wrong size causes the picture to look janky or dark as it does on the screenshots above. No worries this will be fixed in a future PR.~~ Thumbs are now generated with a size parameter - UI Icons will need to be implemented in a future PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ This is not a vibe coded PR. Copilot was used for autocompletion to save time but I reviewed, understood and edited all generated code. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-05 17:50:11 +07:00
size_t MyLibraryActivity::findEntry(const std::string& name) const {
for (size_t i = 0; i < files.size(); i++)
if (files[i] == name) return i;
return 0;
}