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"
|
|
|
|
|
|
2026-01-24 02:01:53 -05:00
|
|
|
#include <Bitmap.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 <GfxRenderer.h>
|
|
|
|
|
#include <SDCardManager.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
2026-01-22 15:45:07 -05:00
|
|
|
#include "BookManager.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 "MappedInputManager.h"
|
|
|
|
|
#include "RecentBooksStore.h"
|
|
|
|
|
#include "ScreenComponents.h"
|
|
|
|
|
#include "fontIds.h"
|
|
|
|
|
#include "util/StringUtils.h"
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
// Layout constants
|
|
|
|
|
constexpr int TAB_BAR_Y = 15;
|
|
|
|
|
constexpr int CONTENT_START_Y = 60;
|
|
|
|
|
constexpr int LINE_HEIGHT = 30;
|
2026-01-23 22:38:59 -05:00
|
|
|
constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items
|
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
|
|
|
constexpr int LEFT_MARGIN = 20;
|
|
|
|
|
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator
|
2026-01-24 02:01:53 -05:00
|
|
|
constexpr int MICRO_THUMB_WIDTH = 45;
|
|
|
|
|
constexpr int MICRO_THUMB_HEIGHT = 60;
|
|
|
|
|
constexpr int THUMB_RIGHT_MARGIN = 50; // Space from right edge for thumbnail
|
|
|
|
|
|
|
|
|
|
// Helper function to get the micro-thumb path for a book based on its file path
|
|
|
|
|
std::string getMicroThumbPathForBook(const std::string& bookPath) {
|
|
|
|
|
// Calculate cache path using same hash method as Epub/Xtc/Txt classes
|
|
|
|
|
const size_t hash = std::hash<std::string>{}(bookPath);
|
|
|
|
|
|
|
|
|
|
if (StringUtils::checkFileExtension(bookPath, ".epub")) {
|
|
|
|
|
return "/.crosspoint/epub_" + std::to_string(hash) + "/micro_thumb.bmp";
|
|
|
|
|
} else if (StringUtils::checkFileExtension(bookPath, ".xtc") || StringUtils::checkFileExtension(bookPath, ".xtch")) {
|
|
|
|
|
return "/.crosspoint/xtc_" + std::to_string(hash) + "/micro_thumb.bmp";
|
|
|
|
|
} else if (StringUtils::checkFileExtension(bookPath, ".txt") || StringUtils::checkFileExtension(bookPath, ".TXT")) {
|
|
|
|
|
return "/.crosspoint/txt_" + std::to_string(hash) + "/micro_thumb.bmp";
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
|
// Timing thresholds
|
|
|
|
|
constexpr int SKIP_PAGE_MS = 700;
|
|
|
|
|
constexpr unsigned long GO_HOME_MS = 1000;
|
2026-01-22 15:45:07 -05:00
|
|
|
constexpr unsigned long ACTION_MENU_MS = 700; // Long press to open action menu
|
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) {
|
|
|
|
|
if (str1.back() == '/' && str2.back() != '/') return true;
|
|
|
|
|
if (str1.back() != '/' && str2.back() == '/') return false;
|
|
|
|
|
return lexicographical_compare(
|
|
|
|
|
begin(str1), end(str1), begin(str2), end(str2),
|
|
|
|
|
[](const char& char1, const char& char2) { return tolower(char1) < tolower(char2); });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
int MyLibraryActivity::getPageItems() const {
|
|
|
|
|
const int screenHeight = renderer.getScreenHeight();
|
|
|
|
|
const int bottomBarHeight = 60; // Space for button hints
|
|
|
|
|
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight;
|
2026-01-23 22:38:59 -05:00
|
|
|
const int lineHeight = (currentTab == Tab::Recent) ? RECENTS_LINE_HEIGHT : LINE_HEIGHT;
|
|
|
|
|
int items = availableHeight / lineHeight;
|
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 (items < 1) {
|
|
|
|
|
items = 1;
|
|
|
|
|
}
|
|
|
|
|
return items;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MyLibraryActivity::getCurrentItemCount() const {
|
|
|
|
|
if (currentTab == Tab::Recent) {
|
2026-01-23 22:38:59 -05:00
|
|
|
return static_cast<int>(recentBooks.size());
|
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 static_cast<int>(files.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MyLibraryActivity::getTotalPages() const {
|
|
|
|
|
const int itemCount = getCurrentItemCount();
|
|
|
|
|
const int pageItems = getPageItems();
|
|
|
|
|
if (itemCount == 0) return 1;
|
|
|
|
|
return (itemCount + pageItems - 1) / pageItems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MyLibraryActivity::getCurrentPage() const {
|
|
|
|
|
const int pageItems = getPageItems();
|
|
|
|
|
return selectorIndex / pageItems + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::loadRecentBooks() {
|
2026-01-23 22:38:59 -05:00
|
|
|
recentBooks.clear();
|
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 auto& books = RECENT_BOOKS.getBooks();
|
2026-01-23 22:38:59 -05:00
|
|
|
recentBooks.reserve(books.size());
|
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
|
|
|
|
2026-01-23 22:38:59 -05:00
|
|
|
for (const auto& book : books) {
|
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
|
|
|
// Skip if file no longer exists
|
2026-01-23 22:38:59 -05:00
|
|
|
if (!SdMan.exists(book.path.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
|
|
|
continue;
|
|
|
|
|
}
|
2026-01-23 22:38:59 -05:00
|
|
|
recentBooks.push_back(book);
|
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 = SdMan.open(basepath.c_str());
|
|
|
|
|
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")) {
|
|
|
|
|
files.emplace_back(filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
file.close();
|
|
|
|
|
}
|
|
|
|
|
root.close();
|
|
|
|
|
sortFileList(files);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::taskTrampoline(void* param) {
|
|
|
|
|
auto* self = static_cast<MyLibraryActivity*>(param);
|
|
|
|
|
self->displayTaskLoop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::onEnter() {
|
|
|
|
|
Activity::onEnter();
|
|
|
|
|
|
|
|
|
|
renderingMutex = xSemaphoreCreateMutex();
|
|
|
|
|
|
|
|
|
|
// Load data for both tabs
|
|
|
|
|
loadRecentBooks();
|
|
|
|
|
loadFiles();
|
|
|
|
|
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
|
|
|
|
|
xTaskCreate(&MyLibraryActivity::taskTrampoline, "MyLibraryActivityTask",
|
|
|
|
|
4096, // Stack size (increased for epub metadata loading)
|
|
|
|
|
this, // Parameters
|
|
|
|
|
1, // Priority
|
|
|
|
|
&displayTaskHandle // Task handle
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::onExit() {
|
|
|
|
|
Activity::onExit();
|
|
|
|
|
|
|
|
|
|
// Wait until not rendering to delete task to avoid killing mid-instruction to
|
|
|
|
|
// EPD
|
|
|
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
|
|
|
if (displayTaskHandle) {
|
|
|
|
|
vTaskDelete(displayTaskHandle);
|
|
|
|
|
displayTaskHandle = nullptr;
|
|
|
|
|
}
|
|
|
|
|
vSemaphoreDelete(renderingMutex);
|
|
|
|
|
renderingMutex = nullptr;
|
|
|
|
|
|
2026-01-23 22:38:59 -05:00
|
|
|
recentBooks.clear();
|
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.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 15:45:07 -05:00
|
|
|
bool MyLibraryActivity::isSelectedItemAFile() const {
|
|
|
|
|
if (currentTab == Tab::Recent) {
|
2026-01-23 22:38:59 -05:00
|
|
|
return !recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size());
|
2026-01-22 15:45:07 -05:00
|
|
|
} else {
|
|
|
|
|
// Files tab - check if it's a file (not a directory)
|
|
|
|
|
if (files.empty() || selectorIndex >= static_cast<int>(files.size())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return files[selectorIndex].back() != '/';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::openActionMenu() {
|
|
|
|
|
if (!isSelectedItemAFile()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentTab == Tab::Recent) {
|
2026-01-23 22:38:59 -05:00
|
|
|
const auto& book = recentBooks[selectorIndex];
|
|
|
|
|
actionTargetPath = book.path;
|
|
|
|
|
// Use title if available, otherwise extract from path
|
|
|
|
|
if (!book.title.empty()) {
|
|
|
|
|
actionTargetName = book.title;
|
|
|
|
|
} else {
|
|
|
|
|
actionTargetName = book.path;
|
|
|
|
|
const size_t lastSlash = actionTargetName.find_last_of('/');
|
|
|
|
|
if (lastSlash != std::string::npos) {
|
|
|
|
|
actionTargetName = actionTargetName.substr(lastSlash + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-22 15:45:07 -05:00
|
|
|
} else {
|
|
|
|
|
if (basepath.back() != '/') {
|
|
|
|
|
actionTargetPath = basepath + "/" + files[selectorIndex];
|
|
|
|
|
} else {
|
|
|
|
|
actionTargetPath = basepath + files[selectorIndex];
|
|
|
|
|
}
|
|
|
|
|
actionTargetName = files[selectorIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uiState = UIState::ActionMenu;
|
|
|
|
|
menuSelection = 0; // Default to Archive
|
|
|
|
|
ignoreNextConfirmRelease = true; // Ignore the release from the long-press that opened this menu
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::executeAction() {
|
|
|
|
|
bool success = false;
|
|
|
|
|
|
|
|
|
|
if (selectedAction == ActionType::Archive) {
|
|
|
|
|
success = BookManager::archiveBook(actionTargetPath);
|
|
|
|
|
} else {
|
|
|
|
|
success = BookManager::deleteBook(actionTargetPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
// Reload data
|
|
|
|
|
loadRecentBooks();
|
|
|
|
|
loadFiles();
|
|
|
|
|
|
|
|
|
|
// Adjust selector if needed
|
|
|
|
|
const int itemCount = getCurrentItemCount();
|
|
|
|
|
if (selectorIndex >= itemCount && itemCount > 0) {
|
|
|
|
|
selectorIndex = itemCount - 1;
|
|
|
|
|
} else if (itemCount == 0) {
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uiState = UIState::Normal;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
}
|
|
|
|
|
|
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::loop() {
|
2026-01-22 15:45:07 -05:00
|
|
|
// Handle action menu state
|
|
|
|
|
if (uiState == UIState::ActionMenu) {
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
|
|
|
|
uiState = UIState::Normal;
|
|
|
|
|
ignoreNextConfirmRelease = false;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Up)) {
|
|
|
|
|
menuSelection = 0; // Archive
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Down)) {
|
|
|
|
|
menuSelection = 1; // Delete
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
|
|
|
// Ignore the release from the long-press that opened this menu
|
|
|
|
|
if (ignoreNextConfirmRelease) {
|
|
|
|
|
ignoreNextConfirmRelease = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
selectedAction = (menuSelection == 0) ? ActionType::Archive : ActionType::Delete;
|
|
|
|
|
uiState = UIState::Confirming;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle confirmation state
|
|
|
|
|
if (uiState == UIState::Confirming) {
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
|
|
|
|
uiState = UIState::ActionMenu;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
|
|
|
|
executeAction();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normal state handling
|
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 int itemCount = getCurrentItemCount();
|
|
|
|
|
const int pageItems = getPageItems();
|
|
|
|
|
|
|
|
|
|
// Long press BACK (1s+) in Files tab goes to root folder
|
|
|
|
|
if (currentTab == Tab::Files && mappedInput.isPressed(MappedInputManager::Button::Back) &&
|
|
|
|
|
mappedInput.getHeldTime() >= GO_HOME_MS) {
|
|
|
|
|
if (basepath != "/") {
|
|
|
|
|
basepath = "/";
|
|
|
|
|
loadFiles();
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 15:45:07 -05:00
|
|
|
// Long press Confirm to open action menu (only for files, not directories)
|
|
|
|
|
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
|
|
|
|
|
mappedInput.getHeldTime() >= ACTION_MENU_MS && isSelectedItemAFile()) {
|
|
|
|
|
openActionMenu();
|
|
|
|
|
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
|
|
|
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Up);
|
|
|
|
|
const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down);
|
|
|
|
|
const bool leftReleased = mappedInput.wasReleased(MappedInputManager::Button::Left);
|
|
|
|
|
const bool rightReleased = mappedInput.wasReleased(MappedInputManager::Button::Right);
|
|
|
|
|
|
|
|
|
|
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
|
|
|
|
|
2026-01-22 15:45:07 -05:00
|
|
|
// Confirm button - open selected item (short press)
|
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)) {
|
2026-01-22 15:45:07 -05:00
|
|
|
// Ignore if it was a long press that triggered the action menu
|
|
|
|
|
if (mappedInput.getHeldTime() >= ACTION_MENU_MS) {
|
|
|
|
|
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 (currentTab == Tab::Recent) {
|
2026-01-23 22:38:59 -05:00
|
|
|
if (!recentBooks.empty() && selectorIndex < static_cast<int>(recentBooks.size())) {
|
|
|
|
|
onSelectBook(recentBooks[selectorIndex].path, currentTab);
|
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 {
|
|
|
|
|
// Files tab
|
|
|
|
|
if (!files.empty() && selectorIndex < static_cast<int>(files.size())) {
|
|
|
|
|
if (basepath.back() != '/') basepath += "/";
|
|
|
|
|
if (files[selectorIndex].back() == '/') {
|
|
|
|
|
// Enter directory
|
|
|
|
|
basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1);
|
|
|
|
|
loadFiles();
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Open file
|
|
|
|
|
onSelectBook(basepath + files[selectorIndex], currentTab);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Back button
|
|
|
|
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
|
|
|
|
if (mappedInput.getHeldTime() < GO_HOME_MS) {
|
|
|
|
|
if (currentTab == Tab::Files && basepath != "/") {
|
|
|
|
|
// Go up one directory, remembering the directory we came from
|
|
|
|
|
const std::string oldPath = basepath;
|
|
|
|
|
basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
|
|
|
|
|
if (basepath.empty()) basepath = "/";
|
|
|
|
|
loadFiles();
|
|
|
|
|
|
|
|
|
|
// Select the directory we just came from
|
|
|
|
|
const auto pos = oldPath.find_last_of('/');
|
|
|
|
|
const std::string dirName = oldPath.substr(pos + 1) + "/";
|
|
|
|
|
selectorIndex = static_cast<int>(findEntry(dirName));
|
|
|
|
|
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Go home
|
|
|
|
|
onGoHome();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tab switching: Left/Right always control tabs
|
|
|
|
|
if (leftReleased && currentTab == Tab::Files) {
|
|
|
|
|
currentTab = Tab::Recent;
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (rightReleased && currentTab == Tab::Recent) {
|
|
|
|
|
currentTab = Tab::Files;
|
|
|
|
|
selectorIndex = 0;
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Navigation: Up/Down moves through items only
|
|
|
|
|
const bool prevReleased = upReleased;
|
|
|
|
|
const bool nextReleased = downReleased;
|
|
|
|
|
|
|
|
|
|
if (prevReleased && itemCount > 0) {
|
|
|
|
|
if (skipPage) {
|
|
|
|
|
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + itemCount) % itemCount;
|
|
|
|
|
} else {
|
|
|
|
|
selectorIndex = (selectorIndex + itemCount - 1) % itemCount;
|
|
|
|
|
}
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
} else if (nextReleased && itemCount > 0) {
|
|
|
|
|
if (skipPage) {
|
|
|
|
|
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % itemCount;
|
|
|
|
|
} else {
|
|
|
|
|
selectorIndex = (selectorIndex + 1) % itemCount;
|
|
|
|
|
}
|
|
|
|
|
updateRequired = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::displayTaskLoop() {
|
|
|
|
|
while (true) {
|
|
|
|
|
if (updateRequired) {
|
|
|
|
|
updateRequired = false;
|
|
|
|
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
|
|
|
render();
|
|
|
|
|
xSemaphoreGive(renderingMutex);
|
|
|
|
|
}
|
|
|
|
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::render() const {
|
|
|
|
|
renderer.clearScreen();
|
|
|
|
|
|
2026-01-22 15:45:07 -05:00
|
|
|
// Handle different UI states
|
|
|
|
|
if (uiState == UIState::ActionMenu) {
|
|
|
|
|
renderActionMenu();
|
|
|
|
|
renderer.displayBuffer();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (uiState == UIState::Confirming) {
|
|
|
|
|
renderConfirmation();
|
|
|
|
|
renderer.displayBuffer();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normal state - draw library view
|
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
|
|
|
// Draw tab bar
|
|
|
|
|
std::vector<TabInfo> tabs = {{"Recent", currentTab == Tab::Recent}, {"Files", currentTab == Tab::Files}};
|
|
|
|
|
ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs);
|
|
|
|
|
|
|
|
|
|
// Draw content based on current tab
|
|
|
|
|
if (currentTab == Tab::Recent) {
|
|
|
|
|
renderRecentTab();
|
|
|
|
|
} else {
|
|
|
|
|
renderFilesTab();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw scroll indicator
|
|
|
|
|
const int screenHeight = renderer.getScreenHeight();
|
|
|
|
|
const int contentHeight = screenHeight - CONTENT_START_Y - 60; // 60 for bottom bar
|
|
|
|
|
ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), CONTENT_START_Y, contentHeight);
|
|
|
|
|
|
|
|
|
|
// Draw side button hints (up/down navigation on right side)
|
|
|
|
|
// Note: text is rotated 90° CW, so ">" appears as "^" and "<" appears as "v"
|
|
|
|
|
renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<");
|
|
|
|
|
|
|
|
|
|
// Draw bottom button hints
|
|
|
|
|
const auto labels = mappedInput.mapLabels("« Back", "Open", "<", ">");
|
|
|
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
|
|
|
|
|
|
renderer.displayBuffer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::renderRecentTab() const {
|
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const int pageItems = getPageItems();
|
2026-01-23 22:38:59 -05:00
|
|
|
const int bookCount = static_cast<int>(recentBooks.size());
|
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 (bookCount == 0) {
|
|
|
|
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
|
|
|
|
|
|
|
|
|
// Draw selection highlight
|
2026-01-23 22:38:59 -05:00
|
|
|
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2,
|
|
|
|
|
pageWidth - RIGHT_MARGIN, RECENTS_LINE_HEIGHT);
|
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
|
|
|
|
2026-01-24 02:01:53 -05:00
|
|
|
// Calculate available text width (leaving space for thumbnail on the right)
|
|
|
|
|
const int textMaxWidth = pageWidth - LEFT_MARGIN - RIGHT_MARGIN - MICRO_THUMB_WIDTH - 10;
|
|
|
|
|
const int thumbX = pageWidth - THUMB_RIGHT_MARGIN - MICRO_THUMB_WIDTH;
|
|
|
|
|
|
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
|
|
|
// Draw items
|
|
|
|
|
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
|
2026-01-23 22:38:59 -05:00
|
|
|
const auto& book = recentBooks[i];
|
|
|
|
|
const int y = CONTENT_START_Y + (i % pageItems) * RECENTS_LINE_HEIGHT;
|
2026-01-24 02:01:53 -05:00
|
|
|
const bool isSelected = (i == selectorIndex);
|
|
|
|
|
|
|
|
|
|
// Try to load and draw micro-thumbnail
|
|
|
|
|
const std::string microThumbPath = getMicroThumbPathForBook(book.path);
|
|
|
|
|
bool hasThumb = false;
|
|
|
|
|
if (!microThumbPath.empty() && SdMan.exists(microThumbPath.c_str())) {
|
|
|
|
|
FsFile thumbFile;
|
|
|
|
|
if (SdMan.openFileForRead("MYL", microThumbPath, thumbFile)) {
|
|
|
|
|
Bitmap bitmap(thumbFile);
|
|
|
|
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
|
|
|
|
// Calculate actual drawn size (scaled to fit within max dimensions, preserving aspect ratio)
|
|
|
|
|
const int bmpW = bitmap.getWidth();
|
|
|
|
|
const int bmpH = bitmap.getHeight();
|
|
|
|
|
const float scaleX = static_cast<float>(MICRO_THUMB_WIDTH) / static_cast<float>(bmpW);
|
|
|
|
|
const float scaleY = static_cast<float>(MICRO_THUMB_HEIGHT) / static_cast<float>(bmpH);
|
|
|
|
|
const float scale = std::min(scaleX, scaleY);
|
|
|
|
|
const int drawnW = static_cast<int>(bmpW * scale);
|
|
|
|
|
const int drawnH = static_cast<int>(bmpH * scale);
|
|
|
|
|
|
|
|
|
|
// Center thumbnail vertically within the row using actual drawn height
|
|
|
|
|
const int thumbY = y + (RECENTS_LINE_HEIGHT - drawnH) / 2;
|
|
|
|
|
// When selected, clear only the actual drawn area to white first
|
|
|
|
|
// (drawBitmap1Bit only draws pixels, it doesn't clear, so we need the white background)
|
|
|
|
|
if (isSelected) {
|
|
|
|
|
renderer.fillRect(thumbX, thumbY, drawnW, drawnH, false);
|
|
|
|
|
}
|
|
|
|
|
renderer.drawBitmap(bitmap, thumbX, thumbY, MICRO_THUMB_WIDTH, MICRO_THUMB_HEIGHT, 0, 0, isSelected);
|
|
|
|
|
hasThumb = true;
|
|
|
|
|
}
|
|
|
|
|
thumbFile.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use full width if no thumbnail, otherwise use reduced width
|
|
|
|
|
const int availableWidth = hasThumb ? textMaxWidth : (pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
|
2026-01-23 22:38:59 -05:00
|
|
|
|
|
|
|
|
// Line 1: Title
|
|
|
|
|
std::string title = book.title;
|
|
|
|
|
if (title.empty()) {
|
|
|
|
|
// Fallback for older entries or files without metadata
|
|
|
|
|
title = book.path;
|
|
|
|
|
const size_t lastSlash = title.find_last_of('/');
|
|
|
|
|
if (lastSlash != std::string::npos) {
|
|
|
|
|
title = title.substr(lastSlash + 1);
|
|
|
|
|
}
|
|
|
|
|
const size_t dot = title.find_last_of('.');
|
|
|
|
|
if (dot != std::string::npos) {
|
|
|
|
|
title.resize(dot);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-24 02:01:53 -05:00
|
|
|
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), availableWidth);
|
|
|
|
|
renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, y + 2, truncatedTitle.c_str(), !isSelected);
|
2026-01-23 22:38:59 -05:00
|
|
|
|
|
|
|
|
// Line 2: Author
|
|
|
|
|
if (!book.author.empty()) {
|
2026-01-24 02:01:53 -05:00
|
|
|
auto truncatedAuthor = renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), availableWidth);
|
|
|
|
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, y + 32, truncatedAuthor.c_str(), !isSelected);
|
2026-01-23 22:38:59 -05: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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::renderFilesTab() const {
|
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const int pageItems = getPageItems();
|
|
|
|
|
const int fileCount = static_cast<int>(files.size());
|
|
|
|
|
|
|
|
|
|
if (fileCount == 0) {
|
|
|
|
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No books found");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
|
|
|
|
|
|
|
|
|
// Draw selection highlight
|
|
|
|
|
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN,
|
|
|
|
|
LINE_HEIGHT);
|
|
|
|
|
|
|
|
|
|
// Draw items
|
|
|
|
|
for (int i = pageStartIndex; i < fileCount && i < pageStartIndex + pageItems; i++) {
|
|
|
|
|
auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
|
|
|
|
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + (i % pageItems) * LINE_HEIGHT, item.c_str(),
|
|
|
|
|
i != selectorIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-22 15:45:07 -05:00
|
|
|
|
|
|
|
|
void MyLibraryActivity::renderActionMenu() const {
|
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const auto pageHeight = renderer.getScreenHeight();
|
|
|
|
|
|
|
|
|
|
// Title
|
|
|
|
|
renderer.drawCenteredText(UI_12_FONT_ID, 20, "Book Actions", true, EpdFontFamily::BOLD);
|
|
|
|
|
|
|
|
|
|
// Show filename
|
|
|
|
|
const int filenameY = 70;
|
|
|
|
|
auto truncatedName = renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40);
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str());
|
|
|
|
|
|
|
|
|
|
// Menu options
|
|
|
|
|
const int menuStartY = pageHeight / 2 - 30;
|
|
|
|
|
constexpr int menuLineHeight = 40;
|
|
|
|
|
constexpr int menuItemWidth = 120;
|
|
|
|
|
const int menuX = (pageWidth - menuItemWidth) / 2;
|
|
|
|
|
|
|
|
|
|
// Archive option
|
|
|
|
|
if (menuSelection == 0) {
|
|
|
|
|
renderer.fillRect(menuX - 10, menuStartY - 5, menuItemWidth + 20, menuLineHeight);
|
|
|
|
|
}
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, menuStartY, "Archive", menuSelection != 0);
|
|
|
|
|
|
|
|
|
|
// Delete option
|
|
|
|
|
if (menuSelection == 1) {
|
|
|
|
|
renderer.fillRect(menuX - 10, menuStartY + menuLineHeight - 5, menuItemWidth + 20, menuLineHeight);
|
|
|
|
|
}
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight, "Delete", menuSelection != 1);
|
|
|
|
|
|
|
|
|
|
// Draw side button hints (up/down navigation)
|
|
|
|
|
renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<");
|
|
|
|
|
|
|
|
|
|
// Draw bottom button hints
|
|
|
|
|
const auto labels = mappedInput.mapLabels("« Cancel", "Select", "", "");
|
|
|
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MyLibraryActivity::renderConfirmation() const {
|
|
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const auto pageHeight = renderer.getScreenHeight();
|
|
|
|
|
|
|
|
|
|
// Title based on action
|
|
|
|
|
const char* actionTitle = (selectedAction == ActionType::Archive) ? "Archive Book?" : "Delete Book?";
|
|
|
|
|
renderer.drawCenteredText(UI_12_FONT_ID, 20, actionTitle, true, EpdFontFamily::BOLD);
|
|
|
|
|
|
|
|
|
|
// Show filename
|
|
|
|
|
const int filenameY = pageHeight / 2 - 40;
|
|
|
|
|
auto truncatedName = renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40);
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str());
|
|
|
|
|
|
|
|
|
|
// Warning text
|
|
|
|
|
const int warningY = pageHeight / 2;
|
|
|
|
|
if (selectedAction == ActionType::Archive) {
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, warningY, "Book will be moved to archive.");
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, warningY + 25, "Reading progress will be saved.");
|
|
|
|
|
} else {
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, warningY, "Book will be permanently deleted!", true, EpdFontFamily::BOLD);
|
|
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, warningY + 25, "This cannot be undone.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw bottom button hints
|
|
|
|
|
const auto labels = mappedInput.mapLabels("« Cancel", "Confirm", "", "");
|
|
|
|
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
|
|
|
|
}
|