mod: adapt mod activities to #774 render() pattern

Migrate 5 mod Activity subclasses from old polling-based
display task pattern to the upstream render() super-class
pattern with freeRTOS notification:

- EpubReaderBookmarkSelectionActivity
- DictionaryWordSelectActivity
- DictionarySuggestionsActivity
- DictionaryDefinitionActivity
- LookedUpWordsActivity

Changes: remove own TaskHandle/SemaphoreHandle/updateRequired,
use requestUpdate() + render(RenderLock&&) override, fix
potential deadlocks around enterNewActivity() calls.

Also fix stale conflict marker in EpubReaderMenuActivity.h.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-16 13:22:40 -05:00
parent f06e3a0a82
commit 02f2474e3b
11 changed files with 77 additions and 260 deletions

View File

@@ -11,40 +11,14 @@
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
void DictionaryDefinitionActivity::taskTrampoline(void* param) {
auto* self = static_cast<DictionaryDefinitionActivity*>(param);
self->displayTaskLoop();
}
void DictionaryDefinitionActivity::displayTaskLoop() {
while (true) {
if (updateRequired) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void DictionaryDefinitionActivity::onEnter() { void DictionaryDefinitionActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
wrapText(); wrapText();
updateRequired = true; requestUpdate();
xTaskCreate(&DictionaryDefinitionActivity::taskTrampoline, "DictDefTask", 4096, this, 1, &displayTaskHandle);
} }
void DictionaryDefinitionActivity::onExit() { void DictionaryDefinitionActivity::onExit() {
Activity::onExit(); Activity::onExit();
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -442,12 +416,12 @@ void DictionaryDefinitionActivity::loop() {
if (prevPage && currentPage > 0) { if (prevPage && currentPage > 0) {
currentPage--; currentPage--;
updateRequired = true; requestUpdate();
} }
if (nextPage && currentPage < totalPages - 1) { if (nextPage && currentPage < totalPages - 1) {
currentPage++; currentPage++;
updateRequired = true; requestUpdate();
} }
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
@@ -465,7 +439,7 @@ void DictionaryDefinitionActivity::loop() {
} }
} }
void DictionaryDefinitionActivity::renderScreen() { void DictionaryDefinitionActivity::render(Activity::RenderLock&&) {
renderer.clearScreen(); renderer.clearScreen();
const bool landscape = orientation == CrossPointSettings::ORIENTATION::LANDSCAPE_CW || const bool landscape = orientation == CrossPointSettings::ORIENTATION::LANDSCAPE_CW ||

View File

@@ -1,8 +1,5 @@
#pragma once #pragma once
#include <EpdFontFamily.h> #include <EpdFontFamily.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <functional> #include <functional>
#include <string> #include <string>
@@ -27,6 +24,7 @@ class DictionaryDefinitionActivity final : public Activity {
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void loop() override; void loop() override;
void render(Activity::RenderLock&&) override;
private: private:
// A positioned text segment within a wrapped line (pre-calculated x offset and style). // A positioned text segment within a wrapped line (pre-calculated x offset and style).
@@ -61,17 +59,10 @@ class DictionaryDefinitionActivity final : public Activity {
int currentPage = 0; int currentPage = 0;
int linesPerPage = 0; int linesPerPage = 0;
int totalPages = 0; int totalPages = 0;
bool updateRequired = false;
bool firstRender = true; bool firstRender = true;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
std::vector<TextAtom> parseHtml(const std::string& html); std::vector<TextAtom> parseHtml(const std::string& html);
static std::string decodeEntity(const std::string& entity); static std::string decodeEntity(const std::string& entity);
static bool isRenderableCodepoint(uint32_t cp); static bool isRenderableCodepoint(uint32_t cp);
void wrapText(); void wrapText();
void renderScreen();
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
}; };

View File

@@ -8,39 +8,13 @@
#include "fontIds.h" #include "fontIds.h"
#include "util/Dictionary.h" #include "util/Dictionary.h"
void DictionarySuggestionsActivity::taskTrampoline(void* param) {
auto* self = static_cast<DictionarySuggestionsActivity*>(param);
self->displayTaskLoop();
}
void DictionarySuggestionsActivity::displayTaskLoop() {
while (true) {
if (updateRequired && !subActivity) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void DictionarySuggestionsActivity::onEnter() { void DictionarySuggestionsActivity::onEnter() {
ActivityWithSubactivity::onEnter(); ActivityWithSubactivity::onEnter();
renderingMutex = xSemaphoreCreateMutex(); requestUpdate();
updateRequired = true;
xTaskCreate(&DictionarySuggestionsActivity::taskTrampoline, "DictSugTask", 4096, this, 1, &displayTaskHandle);
} }
void DictionarySuggestionsActivity::onExit() { void DictionarySuggestionsActivity::onExit() {
ActivityWithSubactivity::onExit(); ActivityWithSubactivity::onExit();
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
} }
void DictionarySuggestionsActivity::loop() { void DictionarySuggestionsActivity::loop() {
@@ -49,7 +23,7 @@ void DictionarySuggestionsActivity::loop() {
if (pendingBackFromDef) { if (pendingBackFromDef) {
pendingBackFromDef = false; pendingBackFromDef = false;
exitActivity(); exitActivity();
updateRequired = true; requestUpdate();
} }
if (pendingExitToReader) { if (pendingExitToReader) {
pendingExitToReader = false; pendingExitToReader = false;
@@ -68,12 +42,12 @@ void DictionarySuggestionsActivity::loop() {
buttonNavigator.onNext([this] { buttonNavigator.onNext([this] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(suggestions.size())); selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(suggestions.size()));
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onPrevious([this] { buttonNavigator.onPrevious([this] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(suggestions.size())); selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(suggestions.size()));
updateRequired = true; requestUpdate();
}); });
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
@@ -81,10 +55,13 @@ void DictionarySuggestionsActivity::loop() {
std::string definition = Dictionary::lookup(selected); std::string definition = Dictionary::lookup(selected);
if (definition.empty()) { if (definition.empty()) {
{
Activity::RenderLock lock(*this);
GUI.drawPopup(renderer, "Not found"); GUI.drawPopup(renderer, "Not found");
renderer.displayBuffer(HalDisplay::FAST_REFRESH); renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
updateRequired = true; requestUpdate();
return; return;
} }
@@ -100,7 +77,7 @@ void DictionarySuggestionsActivity::loop() {
} }
} }
void DictionarySuggestionsActivity::renderScreen() { void DictionarySuggestionsActivity::render(Activity::RenderLock&&) {
renderer.clearScreen(); renderer.clearScreen();
const auto orient = renderer.getOrientation(); const auto orient = renderer.getOrientation();

View File

@@ -1,8 +1,4 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -28,6 +24,7 @@ class DictionarySuggestionsActivity final : public ActivityWithSubactivity {
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void loop() override; void loop() override;
void render(Activity::RenderLock&&) override;
private: private:
std::string originalWord; std::string originalWord;
@@ -39,15 +36,7 @@ class DictionarySuggestionsActivity final : public ActivityWithSubactivity {
const std::function<void()> onDone; const std::function<void()> onDone;
int selectedIndex = 0; int selectedIndex = 0;
bool updateRequired = false;
bool pendingBackFromDef = false; bool pendingBackFromDef = false;
bool pendingExitToReader = false; bool pendingExitToReader = false;
ButtonNavigator buttonNavigator; ButtonNavigator buttonNavigator;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
void renderScreen();
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
}; };

View File

@@ -14,45 +14,19 @@
#include "util/Dictionary.h" #include "util/Dictionary.h"
#include "util/LookupHistory.h" #include "util/LookupHistory.h"
void DictionaryWordSelectActivity::taskTrampoline(void* param) {
auto* self = static_cast<DictionaryWordSelectActivity*>(param);
self->displayTaskLoop();
}
void DictionaryWordSelectActivity::displayTaskLoop() {
while (true) {
if (updateRequired && !subActivity) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void DictionaryWordSelectActivity::onEnter() { void DictionaryWordSelectActivity::onEnter() {
ActivityWithSubactivity::onEnter(); ActivityWithSubactivity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
extractWords(); extractWords();
mergeHyphenatedWords(); mergeHyphenatedWords();
if (!rows.empty()) { if (!rows.empty()) {
currentRow = static_cast<int>(rows.size()) / 3; currentRow = static_cast<int>(rows.size()) / 3;
currentWordInRow = 0; currentWordInRow = 0;
} }
updateRequired = true; requestUpdate();
xTaskCreate(&DictionaryWordSelectActivity::taskTrampoline, "DictWordSelTask", 4096, this, 1, &displayTaskHandle);
} }
void DictionaryWordSelectActivity::onExit() { void DictionaryWordSelectActivity::onExit() {
ActivityWithSubactivity::onExit(); ActivityWithSubactivity::onExit();
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
} }
bool DictionaryWordSelectActivity::isLandscape() const { bool DictionaryWordSelectActivity::isLandscape() const {
@@ -231,7 +205,7 @@ void DictionaryWordSelectActivity::loop() {
if (pendingBackFromDef) { if (pendingBackFromDef) {
pendingBackFromDef = false; pendingBackFromDef = false;
exitActivity(); exitActivity();
updateRequired = true; requestUpdate();
} }
if (pendingExitToReader) { if (pendingExitToReader) {
pendingExitToReader = false; pendingExitToReader = false;
@@ -353,25 +327,28 @@ void DictionaryWordSelectActivity::loop() {
std::string cleaned = Dictionary::cleanWord(rawWord); std::string cleaned = Dictionary::cleanWord(rawWord);
if (cleaned.empty()) { if (cleaned.empty()) {
{
Activity::RenderLock lock(*this);
GUI.drawPopup(renderer, "No word"); GUI.drawPopup(renderer, "No word");
renderer.displayBuffer(HalDisplay::FAST_REFRESH); renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
updateRequired = true; requestUpdate();
return; return;
} }
// Show looking up popup, then release mutex so display task can run Rect popupLayout;
xSemaphoreTake(renderingMutex, portMAX_DELAY); {
Rect popupLayout = GUI.drawPopup(renderer, "Looking up..."); Activity::RenderLock lock(*this);
xSemaphoreGive(renderingMutex); popupLayout = GUI.drawPopup(renderer, "Looking up...");
}
bool cancelled = false; bool cancelled = false;
std::string definition = Dictionary::lookup( std::string definition = Dictionary::lookup(
cleaned, cleaned,
[this, &popupLayout](int percent) { [this, &popupLayout](int percent) {
xSemaphoreTake(renderingMutex, portMAX_DELAY); Activity::RenderLock lock(*this);
GUI.fillPopupProgress(renderer, popupLayout, percent); GUI.fillPopupProgress(renderer, popupLayout, percent);
xSemaphoreGive(renderingMutex);
}, },
[this, &cancelled]() -> bool { [this, &cancelled]() -> bool {
mappedInput.update(); mappedInput.update();
@@ -383,7 +360,7 @@ void DictionaryWordSelectActivity::loop() {
}); });
if (cancelled) { if (cancelled) {
updateRequired = true; requestUpdate();
return; return;
} }
@@ -417,10 +394,13 @@ void DictionaryWordSelectActivity::loop() {
return; return;
} }
{
Activity::RenderLock lock(*this);
GUI.drawPopup(renderer, "Not found"); GUI.drawPopup(renderer, "Not found");
renderer.displayBuffer(HalDisplay::FAST_REFRESH); renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
vTaskDelay(1500 / portTICK_PERIOD_MS); vTaskDelay(1500 / portTICK_PERIOD_MS);
updateRequired = true; requestUpdate();
return; return;
} }
@@ -430,11 +410,11 @@ void DictionaryWordSelectActivity::loop() {
} }
if (changed) { if (changed) {
updateRequired = true; requestUpdate();
} }
} }
void DictionaryWordSelectActivity::renderScreen() { void DictionaryWordSelectActivity::render(Activity::RenderLock&&) {
renderer.clearScreen(); renderer.clearScreen();
// Render the page content // Render the page content

View File

@@ -1,8 +1,5 @@
#pragma once #pragma once
#include <Epub/Page.h> #include <Epub/Page.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
@@ -31,6 +28,7 @@ class DictionaryWordSelectActivity final : public ActivityWithSubactivity {
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void loop() override; void loop() override;
void render(Activity::RenderLock&&) override;
private: private:
struct WordInfo { struct WordInfo {
@@ -64,19 +62,12 @@ class DictionaryWordSelectActivity final : public ActivityWithSubactivity {
std::vector<Row> rows; std::vector<Row> rows;
int currentRow = 0; int currentRow = 0;
int currentWordInRow = 0; int currentWordInRow = 0;
bool updateRequired = false;
bool pendingBackFromDef = false; bool pendingBackFromDef = false;
bool pendingExitToReader = false; bool pendingExitToReader = false;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
bool isLandscape() const; bool isLandscape() const;
bool isInverted() const; bool isInverted() const;
void extractWords(); void extractWords();
void mergeHyphenatedWords(); void mergeHyphenatedWords();
void renderScreen();
void drawHints(); void drawHints();
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
}; };

View File

@@ -42,36 +42,13 @@ std::string EpubReaderBookmarkSelectionActivity::getPageSuffix(const Bookmark& b
return " - Page " + std::to_string(bookmark.pageNumber + 1); return " - Page " + std::to_string(bookmark.pageNumber + 1);
} }
void EpubReaderBookmarkSelectionActivity::taskTrampoline(void* param) {
auto* self = static_cast<EpubReaderBookmarkSelectionActivity*>(param);
self->displayTaskLoop();
}
void EpubReaderBookmarkSelectionActivity::onEnter() { void EpubReaderBookmarkSelectionActivity::onEnter() {
ActivityWithSubactivity::onEnter(); ActivityWithSubactivity::onEnter();
requestUpdate();
renderingMutex = xSemaphoreCreateMutex();
// Trigger first update
updateRequired = true;
xTaskCreate(&EpubReaderBookmarkSelectionActivity::taskTrampoline, "BookmarkSelTask",
4096, // Stack size
this, // Parameters
1, // Priority
&displayTaskHandle // Task handle
);
} }
void EpubReaderBookmarkSelectionActivity::onExit() { void EpubReaderBookmarkSelectionActivity::onExit() {
ActivityWithSubactivity::onExit(); ActivityWithSubactivity::onExit();
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
} }
void EpubReaderBookmarkSelectionActivity::loop() { void EpubReaderBookmarkSelectionActivity::loop() {
@@ -83,7 +60,6 @@ void EpubReaderBookmarkSelectionActivity::loop() {
const int totalItems = getTotalItems(); const int totalItems = getTotalItems();
if (totalItems == 0) { if (totalItems == 0) {
// All bookmarks deleted, go back
if (mappedInput.wasReleased(MappedInputManager::Button::Back) || if (mappedInput.wasReleased(MappedInputManager::Button::Back) ||
mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
onGoBack(); onGoBack();
@@ -91,14 +67,11 @@ void EpubReaderBookmarkSelectionActivity::loop() {
return; return;
} }
// Delete confirmation mode: wait for confirm (delete) or back (cancel)
if (deleteConfirmMode) { if (deleteConfirmMode) {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (ignoreNextConfirmRelease) { if (ignoreNextConfirmRelease) {
// Ignore the release from the initial long press
ignoreNextConfirmRelease = false; ignoreNextConfirmRelease = false;
} else { } else {
// Confirm delete
BookmarkStore::removeBookmark(cachePath, bookmarks[pendingDeleteIndex].spineIndex, BookmarkStore::removeBookmark(cachePath, bookmarks[pendingDeleteIndex].spineIndex,
bookmarks[pendingDeleteIndex].pageNumber); bookmarks[pendingDeleteIndex].pageNumber);
bookmarks.erase(bookmarks.begin() + pendingDeleteIndex); bookmarks.erase(bookmarks.begin() + pendingDeleteIndex);
@@ -106,25 +79,24 @@ void EpubReaderBookmarkSelectionActivity::loop() {
selectorIndex = std::max(0, static_cast<int>(bookmarks.size()) - 1); selectorIndex = std::max(0, static_cast<int>(bookmarks.size()) - 1);
} }
deleteConfirmMode = false; deleteConfirmMode = false;
updateRequired = true; requestUpdate();
} }
} }
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
deleteConfirmMode = false; deleteConfirmMode = false;
ignoreNextConfirmRelease = false; ignoreNextConfirmRelease = false;
updateRequired = true; requestUpdate();
} }
return; return;
} }
// Detect long press on Confirm to trigger delete
constexpr unsigned long DELETE_HOLD_MS = 700; constexpr unsigned long DELETE_HOLD_MS = 700;
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= DELETE_HOLD_MS) { if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= DELETE_HOLD_MS) {
if (totalItems > 0 && selectorIndex >= 0 && selectorIndex < totalItems) { if (totalItems > 0 && selectorIndex >= 0 && selectorIndex < totalItems) {
deleteConfirmMode = true; deleteConfirmMode = true;
ignoreNextConfirmRelease = true; ignoreNextConfirmRelease = true;
pendingDeleteIndex = selectorIndex; pendingDeleteIndex = selectorIndex;
updateRequired = true; requestUpdate();
} }
return; return;
} }
@@ -144,38 +116,26 @@ void EpubReaderBookmarkSelectionActivity::loop() {
buttonNavigator.onNextRelease([this, totalItems] { buttonNavigator.onNextRelease([this, totalItems] {
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onPreviousRelease([this, totalItems] { buttonNavigator.onPreviousRelease([this, totalItems] {
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onNextContinuous([this, totalItems, pageItems] { buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
updateRequired = true; requestUpdate();
}); });
} }
void EpubReaderBookmarkSelectionActivity::displayTaskLoop() { void EpubReaderBookmarkSelectionActivity::render(Activity::RenderLock&&) {
while (true) {
if (updateRequired && !subActivity) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void EpubReaderBookmarkSelectionActivity::renderScreen() {
renderer.clearScreen(); renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
@@ -191,7 +151,6 @@ void EpubReaderBookmarkSelectionActivity::renderScreen() {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int totalItems = getTotalItems(); const int totalItems = getTotalItems();
// Title
const int titleX = const int titleX =
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Go to Bookmark", EpdFontFamily::BOLD)) / 2; contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Go to Bookmark", EpdFontFamily::BOLD)) / 2;
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Go to Bookmark", true, EpdFontFamily::BOLD); renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Go to Bookmark", true, EpdFontFamily::BOLD);
@@ -213,7 +172,6 @@ void EpubReaderBookmarkSelectionActivity::renderScreen() {
const std::string suffix = getPageSuffix(bookmarks[itemIndex]); const std::string suffix = getPageSuffix(bookmarks[itemIndex]);
const int suffixWidth = renderer.getTextWidth(UI_10_FONT_ID, suffix.c_str()); const int suffixWidth = renderer.getTextWidth(UI_10_FONT_ID, suffix.c_str());
// Truncate the prefix (chapter + snippet) to leave room for the page suffix
const std::string prefix = getBookmarkPrefix(bookmarks[itemIndex]); const std::string prefix = getBookmarkPrefix(bookmarks[itemIndex]);
const std::string truncatedPrefix = const std::string truncatedPrefix =
renderer.truncatedText(UI_10_FONT_ID, prefix.c_str(), maxLabelWidth - suffixWidth); renderer.truncatedText(UI_10_FONT_ID, prefix.c_str(), maxLabelWidth - suffixWidth);
@@ -225,7 +183,6 @@ void EpubReaderBookmarkSelectionActivity::renderScreen() {
} }
if (deleteConfirmMode && pendingDeleteIndex < static_cast<int>(bookmarks.size())) { if (deleteConfirmMode && pendingDeleteIndex < static_cast<int>(bookmarks.size())) {
// Draw delete confirmation overlay
const std::string suffix = getPageSuffix(bookmarks[pendingDeleteIndex]); const std::string suffix = getPageSuffix(bookmarks[pendingDeleteIndex]);
std::string msg = "Delete bookmark" + suffix + "?"; std::string msg = "Delete bookmark" + suffix + "?";

View File

@@ -1,8 +1,5 @@
#pragma once #pragma once
#include <Epub.h> #include <Epub.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <memory> #include <memory>
#include <vector> #include <vector>
@@ -15,32 +12,19 @@ class EpubReaderBookmarkSelectionActivity final : public ActivityWithSubactivity
std::shared_ptr<Epub> epub; std::shared_ptr<Epub> epub;
std::vector<Bookmark> bookmarks; std::vector<Bookmark> bookmarks;
std::string cachePath; std::string cachePath;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
ButtonNavigator buttonNavigator; ButtonNavigator buttonNavigator;
int selectorIndex = 0; int selectorIndex = 0;
bool updateRequired = false;
bool deleteConfirmMode = false; bool deleteConfirmMode = false;
bool ignoreNextConfirmRelease = false; bool ignoreNextConfirmRelease = false;
int pendingDeleteIndex = 0; int pendingDeleteIndex = 0;
const std::function<void()> onGoBack; const std::function<void()> onGoBack;
const std::function<void(int newSpineIndex, int newPage)> onSelectBookmark; const std::function<void(int newSpineIndex, int newPage)> onSelectBookmark;
// Number of items that fit on a page, derived from logical screen height.
int getPageItems() const; int getPageItems() const;
int getTotalItems() const; int getTotalItems() const;
// Build the prefix portion of a bookmark label (chapter + snippet, without page suffix)
std::string getBookmarkPrefix(const Bookmark& bookmark) const; std::string getBookmarkPrefix(const Bookmark& bookmark) const;
// Build the page suffix (e.g. " - Page 5")
static std::string getPageSuffix(const Bookmark& bookmark); static std::string getPageSuffix(const Bookmark& bookmark);
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void renderScreen();
public: public:
explicit EpubReaderBookmarkSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit EpubReaderBookmarkSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::shared_ptr<Epub>& epub, const std::shared_ptr<Epub>& epub,
@@ -57,4 +41,5 @@ class EpubReaderBookmarkSelectionActivity final : public ActivityWithSubactivity
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void loop() override; void loop() override;
void render(Activity::RenderLock&&) override;
}; };

View File

@@ -82,7 +82,6 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
const std::function<void(uint8_t)> onBack; const std::function<void(uint8_t)> onBack;
const std::function<void(MenuAction)> onAction; const std::function<void(MenuAction)> onAction;
<<<<<<< HEAD
// Map the internal override value to an index into letterboxFillLabels. // Map the internal override value to an index into letterboxFillLabels.
int letterboxFillToIndex() const { int letterboxFillToIndex() const {

View File

@@ -12,41 +12,15 @@
#include "util/Dictionary.h" #include "util/Dictionary.h"
#include "util/LookupHistory.h" #include "util/LookupHistory.h"
void LookedUpWordsActivity::taskTrampoline(void* param) {
auto* self = static_cast<LookedUpWordsActivity*>(param);
self->displayTaskLoop();
}
void LookedUpWordsActivity::displayTaskLoop() {
while (true) {
if (updateRequired && !subActivity) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void LookedUpWordsActivity::onEnter() { void LookedUpWordsActivity::onEnter() {
ActivityWithSubactivity::onEnter(); ActivityWithSubactivity::onEnter();
renderingMutex = xSemaphoreCreateMutex();
words = LookupHistory::load(cachePath); words = LookupHistory::load(cachePath);
std::reverse(words.begin(), words.end()); std::reverse(words.begin(), words.end());
updateRequired = true; requestUpdate();
xTaskCreate(&LookedUpWordsActivity::taskTrampoline, "LookedUpTask", 4096, this, 1, &displayTaskHandle);
} }
void LookedUpWordsActivity::onExit() { void LookedUpWordsActivity::onExit() {
ActivityWithSubactivity::onExit(); ActivityWithSubactivity::onExit();
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr;
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
} }
void LookedUpWordsActivity::loop() { void LookedUpWordsActivity::loop() {
@@ -55,7 +29,7 @@ void LookedUpWordsActivity::loop() {
if (pendingBackFromDef) { if (pendingBackFromDef) {
pendingBackFromDef = false; pendingBackFromDef = false;
exitActivity(); exitActivity();
updateRequired = true; requestUpdate();
} }
if (pendingExitToReader) { if (pendingExitToReader) {
pendingExitToReader = false; pendingExitToReader = false;
@@ -87,13 +61,13 @@ void LookedUpWordsActivity::loop() {
selectedIndex = std::max(0, static_cast<int>(words.size()) - 1); selectedIndex = std::max(0, static_cast<int>(words.size()) - 1);
} }
deleteConfirmMode = false; deleteConfirmMode = false;
updateRequired = true; requestUpdate();
} }
} }
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
deleteConfirmMode = false; deleteConfirmMode = false;
ignoreNextConfirmRelease = false; ignoreNextConfirmRelease = false;
updateRequired = true; requestUpdate();
} }
return; return;
} }
@@ -104,7 +78,7 @@ void LookedUpWordsActivity::loop() {
deleteConfirmMode = true; deleteConfirmMode = true;
ignoreNextConfirmRelease = true; ignoreNextConfirmRelease = true;
pendingDeleteIndex = selectedIndex; pendingDeleteIndex = selectedIndex;
updateRequired = true; requestUpdate();
return; return;
} }
@@ -113,30 +87,37 @@ void LookedUpWordsActivity::loop() {
buttonNavigator.onNextRelease([this, totalItems] { buttonNavigator.onNextRelease([this, totalItems] {
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, totalItems); selectedIndex = ButtonNavigator::nextIndex(selectedIndex, totalItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onPreviousRelease([this, totalItems] { buttonNavigator.onPreviousRelease([this, totalItems] {
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, totalItems); selectedIndex = ButtonNavigator::previousIndex(selectedIndex, totalItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onNextContinuous([this, totalItems, pageItems] { buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
selectedIndex = ButtonNavigator::nextPageIndex(selectedIndex, totalItems, pageItems); selectedIndex = ButtonNavigator::nextPageIndex(selectedIndex, totalItems, pageItems);
updateRequired = true; requestUpdate();
}); });
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
selectedIndex = ButtonNavigator::previousPageIndex(selectedIndex, totalItems, pageItems); selectedIndex = ButtonNavigator::previousPageIndex(selectedIndex, totalItems, pageItems);
updateRequired = true; requestUpdate();
}); });
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
const std::string& headword = words[selectedIndex]; const std::string& headword = words[selectedIndex];
Rect popupLayout = GUI.drawPopup(renderer, "Looking up..."); Rect popupLayout;
{
Activity::RenderLock lock(*this);
popupLayout = GUI.drawPopup(renderer, "Looking up...");
}
std::string definition = Dictionary::lookup( std::string definition = Dictionary::lookup(
headword, [this, &popupLayout](int percent) { GUI.fillPopupProgress(renderer, popupLayout, percent); }); headword, [this, &popupLayout](int percent) {
Activity::RenderLock lock(*this);
GUI.fillPopupProgress(renderer, popupLayout, percent);
});
if (!definition.empty()) { if (!definition.empty()) {
enterNewActivity(new DictionaryDefinitionActivity( enterNewActivity(new DictionaryDefinitionActivity(
@@ -166,10 +147,13 @@ void LookedUpWordsActivity::loop() {
return; return;
} }
{
Activity::RenderLock lock(*this);
GUI.drawPopup(renderer, "Not found"); GUI.drawPopup(renderer, "Not found");
renderer.displayBuffer(HalDisplay::FAST_REFRESH); renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
vTaskDelay(1500 / portTICK_PERIOD_MS); vTaskDelay(1500 / portTICK_PERIOD_MS);
updateRequired = true; requestUpdate();
return; return;
} }
@@ -190,7 +174,7 @@ int LookedUpWordsActivity::getPageItems() const {
return std::max(1, contentHeight / metrics.listRowHeight); return std::max(1, contentHeight / metrics.listRowHeight);
} }
void LookedUpWordsActivity::renderScreen() { void LookedUpWordsActivity::render(Activity::RenderLock&&) {
renderer.clearScreen(); renderer.clearScreen();
const auto orient = renderer.getOrientation(); const auto orient = renderer.getOrientation();

View File

@@ -1,8 +1,4 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -25,6 +21,7 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
void loop() override; void loop() override;
void render(Activity::RenderLock&&) override;
private: private:
std::string cachePath; std::string cachePath;
@@ -35,7 +32,6 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
std::vector<std::string> words; std::vector<std::string> words;
int selectedIndex = 0; int selectedIndex = 0;
bool updateRequired = false;
bool pendingBackFromDef = false; bool pendingBackFromDef = false;
bool pendingExitToReader = false; bool pendingExitToReader = false;
ButtonNavigator buttonNavigator; ButtonNavigator buttonNavigator;
@@ -45,11 +41,5 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
bool ignoreNextConfirmRelease = false; bool ignoreNextConfirmRelease = false;
int pendingDeleteIndex = 0; int pendingDeleteIndex = 0;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
int getPageItems() const; int getPageItems() const;
void renderScreen();
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
}; };