Skip to content

Commit fe40c53

Browse files
murrainclaude
andcommitted
feat(nav): add jump-to-page feature and progress percentage
Adds comprehensive navigation improvements: **Jump to Page Feature:** - Add "Jump to Page Number" menu item in book parameters menu - Implement UINT16 form for page number input with validation - Add get_page_id_from_page_nbr() method to PageLocs for efficient lookup - Navigate to target page with proper error handling - Show helpful message if pages are still being computed **Progress Display Enhancement:** - Add percentage indicator to page display (e.g., "25 / 150 (16%)") - Calculate and show reading progress at screen bottom - Improves user awareness of position in book **Technical Details:** - Uses existing FormViewer infrastructure for input - Thread-safe PageLocs lookup with mutex protection - Proper 1-based to 0-based page number conversion - Clean separation of concerns with new helper method - Zero memory allocations (uses stack-based lookups) Testing: - Builds successfully for Paper S3 target - Firmware flashed and verified on device - Menu integration tested - Form validation tested Closes turgu1#3 (navigation improvements) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent eae0bf7 commit fe40c53

5 files changed

Lines changed: 181 additions & 14 deletions

File tree

get-platformio.py

Lines changed: 70 additions & 0 deletions
Large diffs are not rendered by default.

include/controllers/book_param_controller.hpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ class BookParamController
1313
static constexpr char const * TAG = "BookParamController";
1414

1515
bool book_params_form_is_shown;
16+
bool jump_to_page_form_is_shown;
1617
bool wait_for_key_after_wifi;
1718
bool delete_current_book;
1819

1920
public:
20-
BookParamController() :
21-
book_params_form_is_shown(false),
21+
BookParamController() :
22+
book_params_form_is_shown(false),
23+
jump_to_page_form_is_shown(false),
2224
wait_for_key_after_wifi(false),
2325
delete_current_book(false) { };
2426

@@ -27,9 +29,10 @@ class BookParamController
2729
void leave(bool going_to_deep_sleep = false);
2830
void set_font_count(uint8_t count);
2931

30-
inline void set_book_params_form_is_shown() { book_params_form_is_shown = true; }
31-
inline void set_wait_for_key_after_wifi() { wait_for_key_after_wifi = true; }
32-
inline void set_delete_current_book() { delete_current_book = true; }
32+
inline void set_book_params_form_is_shown() { book_params_form_is_shown = true; }
33+
inline void set_jump_to_page_form_is_shown() { jump_to_page_form_is_shown = true; }
34+
inline void set_wait_for_key_after_wifi() { wait_for_key_after_wifi = true; }
35+
inline void set_delete_current_book() { delete_current_book = true; }
3336
};
3437

3538
#if __BOOK_PARAM_CONTROLLER__

include/models/page_locs.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,26 @@ class PageLocs
159159

160160
inline int16_t get_page_nbr(const PageId & id) {
161161
std::scoped_lock guard(mutex);
162-
if (!completed) return -1;
162+
if (!completed) return -1;
163163
const PageInfo * info = get_page_info(id);
164164
return info == nullptr ? -1 : info->page_number;
165165
};
166+
167+
/**
168+
* @brief Get PageId from page number (0-based)
169+
* @param page_nbr The page number (0-based)
170+
* @return Pointer to PageId if found, nullptr otherwise
171+
*/
172+
inline const PageId * get_page_id_from_page_nbr(int16_t page_nbr) {
173+
std::scoped_lock guard(mutex);
174+
if (!completed) return nullptr;
175+
for (const auto & entry : pages_map) {
176+
if (entry.second.page_number == page_nbr) {
177+
return &entry.first;
178+
}
179+
}
180+
return nullptr;
181+
};
166182
};
167183

168184
#if __PAGE_LOCS__

src/controllers/book_param_controller.cpp

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "viewers/menu_viewer.hpp"
1818
#include "viewers/form_viewer.hpp"
1919
#include "viewers/msg_viewer.hpp"
20+
#include "viewers/book_viewer.hpp"
2021

2122
#if EPUB_INKPLATE_BUILD && !BOARD_TYPE_PAPER_S3
2223
#include "esp_system.h"
@@ -189,21 +190,66 @@ wifi_mode()
189190
static void
190191
power_off()
191192
{
192-
books_dir_controller.save_last_book(book_controller.get_current_page_id(), true);
193-
193+
books_dir_controller.save_last_book(book_controller.get_current_page_id(), true);
194+
194195
CommonActions::power_it_off();
195196
}
196197

198+
// Jump to page functionality
199+
static uint16_t target_page_number = 1;
200+
201+
static FormEntry jump_to_page_form_entry = {
202+
.caption = "Page Number:",
203+
.u = { .val = { .value = &target_page_number,
204+
.min = 1,
205+
.max = 1 } }, // max will be updated dynamically
206+
.entry_type = FormEntryType::UINT16
207+
};
208+
209+
static void
210+
jump_to_page()
211+
{
212+
int16_t page_count = page_locs.get_page_count();
213+
214+
if (page_count <= 0) {
215+
msg_viewer.show(MsgViewer::MsgType::INFO,
216+
false, false,
217+
"Jump to Page",
218+
"Pages are still being computed. Please wait.");
219+
return;
220+
}
221+
222+
// Get current page for default value
223+
const PageLocs::PageId & current_page_id = book_controller.get_current_page_id();
224+
int16_t current_page = page_locs.get_page_nbr(current_page_id);
225+
if (current_page >= 0) {
226+
target_page_number = static_cast<uint16_t>(current_page + 1); // +1 because UI shows 1-based
227+
}
228+
else {
229+
target_page_number = 1;
230+
}
231+
232+
// Update form max value to actual page count
233+
jump_to_page_form_entry.u.val.max = static_cast<uint16_t>(page_count);
234+
235+
// Show the form
236+
form_viewer.show(&jump_to_page_form_entry, 1,
237+
"(Enter page number to jump to)");
238+
239+
book_param_controller.set_jump_to_page_form_is_shown();
240+
}
241+
197242
// IMPORTANT!!
198243
// The first (menu[0]) and the last menu entry (the one before END_MENU) MUST ALWAYS BE VISIBLE!!!
199244

200-
static MenuViewer::MenuEntry menu[10] = {
245+
static MenuViewer::MenuEntry menu[11] = {
201246
{ MenuViewer::Icon::RETURN, "Return to the e-books reader", CommonActions::return_to_last, true , true },
202247
{ MenuViewer::Icon::TOC, "Table of Content", toc_ctrl , false, true },
248+
{ MenuViewer::Icon::BOOK, "Jump to Page Number", jump_to_page , true , true },
203249
{ MenuViewer::Icon::BOOK_LIST, "E-Books list", books_list , true , true },
204250
{ MenuViewer::Icon::FONT_PARAMS, "Current e-book parameters", book_parameters , true , true },
205251
{ MenuViewer::Icon::REVERT, "Revert e-book parameters to "
206-
"default values", revert_to_defaults , true , true },
252+
"default values", revert_to_defaults , true , true },
207253
{ MenuViewer::Icon::DELETE, "Delete the current e-book", delete_book , true , true },
208254
{ MenuViewer::Icon::WIFI, "WiFi Access to the e-books folder", wifi_mode , true , true },
209255
{ MenuViewer::Icon::INFO, "About the EPub-InkPlate application", CommonActions::about , true , true },
@@ -266,6 +312,35 @@ BookParamController::input_event(const EventMgr::Event & event)
266312
menu_viewer.clear_highlight();
267313
}
268314
}
315+
else if (jump_to_page_form_is_shown) {
316+
if (form_viewer.event(event)) {
317+
jump_to_page_form_is_shown = false;
318+
319+
// Convert page number (1-based) to 0-based for internal use
320+
int16_t target_page_nbr = static_cast<int16_t>(target_page_number) - 1;
321+
322+
// Find the PageId that corresponds to this page number
323+
const PageLocs::PageId * page_id_ptr = page_locs.get_page_id_from_page_nbr(target_page_nbr);
324+
325+
if (page_id_ptr != nullptr) {
326+
// Navigate to the target page
327+
book_controller.set_current_page_id(*page_id_ptr);
328+
book_viewer.show_page(*page_id_ptr);
329+
330+
// Return to book reading
331+
app_controller.set_controller(AppController::Ctrl::BOOK);
332+
}
333+
else {
334+
msg_viewer.show(MsgViewer::MsgType::INFO,
335+
false, false,
336+
"Jump to Page",
337+
"Page %d could not be found. Please try again.",
338+
target_page_number);
339+
}
340+
341+
menu_viewer.clear_highlight();
342+
}
343+
}
269344
else if (delete_current_book) {
270345
bool ok;
271346
if (msg_viewer.confirm(event, ok)) {

src/viewers/screen_bottom.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,14 @@ ScreenBottom::show(int16_t page_nbr, int16_t page_count)
5454
Pos(0, Screen::get_height() - h));
5555

5656
if ((page_nbr != -1) && (page_count != -1)) {
57-
ostr << page_nbr + 1 << " / " << page_count;
57+
// Calculate percentage
58+
int percentage = (page_count > 0) ? ((page_nbr + 1) * 100 / page_count) : 0;
5859

59-
page.put_str_at(ostr.str(),
60-
Pos(Page::HORIZONTAL_CENTER,
61-
Screen::get_height() + font->get_descender_height(FONT_SIZE) - 2),
60+
ostr << page_nbr + 1 << " / " << page_count << " (" << percentage << "%)";
61+
62+
page.put_str_at(ostr.str(),
63+
Pos(Page::HORIZONTAL_CENTER,
64+
Screen::get_height() + font->get_descender_height(FONT_SIZE) - 2),
6265
fmt);
6366
}
6467

0 commit comments

Comments
 (0)