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:
@@ -11,40 +11,14 @@
|
||||
#include "components/UITheme.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() {
|
||||
Activity::onEnter();
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
wrapText();
|
||||
updateRequired = true;
|
||||
xTaskCreate(&DictionaryDefinitionActivity::taskTrampoline, "DictDefTask", 4096, this, 1, &displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void DictionaryDefinitionActivity::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) {
|
||||
currentPage--;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
if (nextPage && currentPage < totalPages - 1) {
|
||||
currentPage++;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
@@ -465,7 +439,7 @@ void DictionaryDefinitionActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void DictionaryDefinitionActivity::renderScreen() {
|
||||
void DictionaryDefinitionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const bool landscape = orientation == CrossPointSettings::ORIENTATION::LANDSCAPE_CW ||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <EpdFontFamily.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -27,6 +24,7 @@ class DictionaryDefinitionActivity final : public Activity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
// 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 linesPerPage = 0;
|
||||
int totalPages = 0;
|
||||
bool updateRequired = false;
|
||||
bool firstRender = true;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
std::vector<TextAtom> parseHtml(const std::string& html);
|
||||
static std::string decodeEntity(const std::string& entity);
|
||||
static bool isRenderableCodepoint(uint32_t cp);
|
||||
void wrapText();
|
||||
void renderScreen();
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
};
|
||||
|
||||
@@ -8,39 +8,13 @@
|
||||
#include "fontIds.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() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
updateRequired = true;
|
||||
xTaskCreate(&DictionarySuggestionsActivity::taskTrampoline, "DictSugTask", 4096, this, 1, &displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void DictionarySuggestionsActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void DictionarySuggestionsActivity::loop() {
|
||||
@@ -49,7 +23,7 @@ void DictionarySuggestionsActivity::loop() {
|
||||
if (pendingBackFromDef) {
|
||||
pendingBackFromDef = false;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
if (pendingExitToReader) {
|
||||
pendingExitToReader = false;
|
||||
@@ -68,12 +42,12 @@ void DictionarySuggestionsActivity::loop() {
|
||||
|
||||
buttonNavigator.onNext([this] {
|
||||
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(suggestions.size()));
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPrevious([this] {
|
||||
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(suggestions.size()));
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
@@ -81,10 +55,13 @@ void DictionarySuggestionsActivity::loop() {
|
||||
std::string definition = Dictionary::lookup(selected);
|
||||
|
||||
if (definition.empty()) {
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
{
|
||||
Activity::RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,7 +77,7 @@ void DictionarySuggestionsActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void DictionarySuggestionsActivity::renderScreen() {
|
||||
void DictionarySuggestionsActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto orient = renderer.getOrientation();
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -28,6 +24,7 @@ class DictionarySuggestionsActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
std::string originalWord;
|
||||
@@ -39,15 +36,7 @@ class DictionarySuggestionsActivity final : public ActivityWithSubactivity {
|
||||
const std::function<void()> onDone;
|
||||
|
||||
int selectedIndex = 0;
|
||||
bool updateRequired = false;
|
||||
bool pendingBackFromDef = false;
|
||||
bool pendingExitToReader = false;
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
void renderScreen();
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
};
|
||||
|
||||
@@ -14,45 +14,19 @@
|
||||
#include "util/Dictionary.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() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
extractWords();
|
||||
mergeHyphenatedWords();
|
||||
if (!rows.empty()) {
|
||||
currentRow = static_cast<int>(rows.size()) / 3;
|
||||
currentWordInRow = 0;
|
||||
}
|
||||
updateRequired = true;
|
||||
xTaskCreate(&DictionaryWordSelectActivity::taskTrampoline, "DictWordSelTask", 4096, this, 1, &displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void DictionaryWordSelectActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
bool DictionaryWordSelectActivity::isLandscape() const {
|
||||
@@ -231,7 +205,7 @@ void DictionaryWordSelectActivity::loop() {
|
||||
if (pendingBackFromDef) {
|
||||
pendingBackFromDef = false;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
if (pendingExitToReader) {
|
||||
pendingExitToReader = false;
|
||||
@@ -353,25 +327,28 @@ void DictionaryWordSelectActivity::loop() {
|
||||
std::string cleaned = Dictionary::cleanWord(rawWord);
|
||||
|
||||
if (cleaned.empty()) {
|
||||
GUI.drawPopup(renderer, "No word");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
{
|
||||
Activity::RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, "No word");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show looking up popup, then release mutex so display task can run
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
Rect popupLayout = GUI.drawPopup(renderer, "Looking up...");
|
||||
xSemaphoreGive(renderingMutex);
|
||||
Rect popupLayout;
|
||||
{
|
||||
Activity::RenderLock lock(*this);
|
||||
popupLayout = GUI.drawPopup(renderer, "Looking up...");
|
||||
}
|
||||
|
||||
bool cancelled = false;
|
||||
std::string definition = Dictionary::lookup(
|
||||
cleaned,
|
||||
[this, &popupLayout](int percent) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
Activity::RenderLock lock(*this);
|
||||
GUI.fillPopupProgress(renderer, popupLayout, percent);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
},
|
||||
[this, &cancelled]() -> bool {
|
||||
mappedInput.update();
|
||||
@@ -383,7 +360,7 @@ void DictionaryWordSelectActivity::loop() {
|
||||
});
|
||||
|
||||
if (cancelled) {
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -417,10 +394,13 @@ void DictionaryWordSelectActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
{
|
||||
Activity::RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
}
|
||||
vTaskDelay(1500 / portTICK_PERIOD_MS);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,11 +410,11 @@ void DictionaryWordSelectActivity::loop() {
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void DictionaryWordSelectActivity::renderScreen() {
|
||||
void DictionaryWordSelectActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
// Render the page content
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Epub/Page.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -31,6 +28,7 @@ class DictionaryWordSelectActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
struct WordInfo {
|
||||
@@ -64,19 +62,12 @@ class DictionaryWordSelectActivity final : public ActivityWithSubactivity {
|
||||
std::vector<Row> rows;
|
||||
int currentRow = 0;
|
||||
int currentWordInRow = 0;
|
||||
bool updateRequired = false;
|
||||
bool pendingBackFromDef = false;
|
||||
bool pendingExitToReader = false;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
bool isLandscape() const;
|
||||
bool isInverted() const;
|
||||
void extractWords();
|
||||
void mergeHyphenatedWords();
|
||||
void renderScreen();
|
||||
void drawHints();
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
};
|
||||
|
||||
@@ -42,36 +42,13 @@ std::string EpubReaderBookmarkSelectionActivity::getPageSuffix(const Bookmark& b
|
||||
return " - Page " + std::to_string(bookmark.pageNumber + 1);
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<EpubReaderBookmarkSelectionActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
xTaskCreate(&EpubReaderBookmarkSelectionActivity::taskTrampoline, "BookmarkSelTask",
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::loop() {
|
||||
@@ -83,7 +60,6 @@ void EpubReaderBookmarkSelectionActivity::loop() {
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
if (totalItems == 0) {
|
||||
// All bookmarks deleted, go back
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back) ||
|
||||
mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
onGoBack();
|
||||
@@ -91,14 +67,11 @@ void EpubReaderBookmarkSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete confirmation mode: wait for confirm (delete) or back (cancel)
|
||||
if (deleteConfirmMode) {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
if (ignoreNextConfirmRelease) {
|
||||
// Ignore the release from the initial long press
|
||||
ignoreNextConfirmRelease = false;
|
||||
} else {
|
||||
// Confirm delete
|
||||
BookmarkStore::removeBookmark(cachePath, bookmarks[pendingDeleteIndex].spineIndex,
|
||||
bookmarks[pendingDeleteIndex].pageNumber);
|
||||
bookmarks.erase(bookmarks.begin() + pendingDeleteIndex);
|
||||
@@ -106,25 +79,24 @@ void EpubReaderBookmarkSelectionActivity::loop() {
|
||||
selectorIndex = std::max(0, static_cast<int>(bookmarks.size()) - 1);
|
||||
}
|
||||
deleteConfirmMode = false;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
deleteConfirmMode = false;
|
||||
ignoreNextConfirmRelease = false;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect long press on Confirm to trigger delete
|
||||
constexpr unsigned long DELETE_HOLD_MS = 700;
|
||||
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= DELETE_HOLD_MS) {
|
||||
if (totalItems > 0 && selectorIndex >= 0 && selectorIndex < totalItems) {
|
||||
deleteConfirmMode = true;
|
||||
ignoreNextConfirmRelease = true;
|
||||
pendingDeleteIndex = selectorIndex;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -144,38 +116,26 @@ void EpubReaderBookmarkSelectionActivity::loop() {
|
||||
|
||||
buttonNavigator.onNextRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void EpubReaderBookmarkSelectionActivity::renderScreen() {
|
||||
void EpubReaderBookmarkSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
@@ -191,7 +151,6 @@ void EpubReaderBookmarkSelectionActivity::renderScreen() {
|
||||
const int pageItems = getPageItems();
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
// Title
|
||||
const int titleX =
|
||||
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);
|
||||
@@ -213,7 +172,6 @@ void EpubReaderBookmarkSelectionActivity::renderScreen() {
|
||||
const std::string suffix = getPageSuffix(bookmarks[itemIndex]);
|
||||
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 truncatedPrefix =
|
||||
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())) {
|
||||
// Draw delete confirmation overlay
|
||||
const std::string suffix = getPageSuffix(bookmarks[pendingDeleteIndex]);
|
||||
std::string msg = "Delete bookmark" + suffix + "?";
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -15,32 +12,19 @@ class EpubReaderBookmarkSelectionActivity final : public ActivityWithSubactivity
|
||||
std::shared_ptr<Epub> epub;
|
||||
std::vector<Bookmark> bookmarks;
|
||||
std::string cachePath;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
ButtonNavigator buttonNavigator;
|
||||
int selectorIndex = 0;
|
||||
bool updateRequired = false;
|
||||
bool deleteConfirmMode = false;
|
||||
bool ignoreNextConfirmRelease = false;
|
||||
int pendingDeleteIndex = 0;
|
||||
const std::function<void()> onGoBack;
|
||||
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 getTotalItems() const;
|
||||
|
||||
// Build the prefix portion of a bookmark label (chapter + snippet, without page suffix)
|
||||
std::string getBookmarkPrefix(const Bookmark& bookmark) const;
|
||||
|
||||
// Build the page suffix (e.g. " - Page 5")
|
||||
static std::string getPageSuffix(const Bookmark& bookmark);
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
|
||||
public:
|
||||
explicit EpubReaderBookmarkSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub,
|
||||
@@ -57,4 +41,5 @@ class EpubReaderBookmarkSelectionActivity final : public ActivityWithSubactivity
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
};
|
||||
|
||||
@@ -82,7 +82,6 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
|
||||
const std::function<void(uint8_t)> onBack;
|
||||
const std::function<void(MenuAction)> onAction;
|
||||
<<<<<<< HEAD
|
||||
|
||||
// Map the internal override value to an index into letterboxFillLabels.
|
||||
int letterboxFillToIndex() const {
|
||||
|
||||
@@ -12,41 +12,15 @@
|
||||
#include "util/Dictionary.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() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
words = LookupHistory::load(cachePath);
|
||||
std::reverse(words.begin(), words.end());
|
||||
updateRequired = true;
|
||||
xTaskCreate(&LookedUpWordsActivity::taskTrampoline, "LookedUpTask", 4096, this, 1, &displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void LookedUpWordsActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void LookedUpWordsActivity::loop() {
|
||||
@@ -55,7 +29,7 @@ void LookedUpWordsActivity::loop() {
|
||||
if (pendingBackFromDef) {
|
||||
pendingBackFromDef = false;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
if (pendingExitToReader) {
|
||||
pendingExitToReader = false;
|
||||
@@ -87,13 +61,13 @@ void LookedUpWordsActivity::loop() {
|
||||
selectedIndex = std::max(0, static_cast<int>(words.size()) - 1);
|
||||
}
|
||||
deleteConfirmMode = false;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
deleteConfirmMode = false;
|
||||
ignoreNextConfirmRelease = false;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -104,7 +78,7 @@ void LookedUpWordsActivity::loop() {
|
||||
deleteConfirmMode = true;
|
||||
ignoreNextConfirmRelease = true;
|
||||
pendingDeleteIndex = selectedIndex;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,30 +87,37 @@ void LookedUpWordsActivity::loop() {
|
||||
|
||||
buttonNavigator.onNextRelease([this, totalItems] {
|
||||
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||
selectedIndex = ButtonNavigator::nextPageIndex(selectedIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||
selectedIndex = ButtonNavigator::previousPageIndex(selectedIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
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(
|
||||
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()) {
|
||||
enterNewActivity(new DictionaryDefinitionActivity(
|
||||
@@ -166,10 +147,13 @@ void LookedUpWordsActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
{
|
||||
Activity::RenderLock lock(*this);
|
||||
GUI.drawPopup(renderer, "Not found");
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
}
|
||||
vTaskDelay(1500 / portTICK_PERIOD_MS);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,7 +174,7 @@ int LookedUpWordsActivity::getPageItems() const {
|
||||
return std::max(1, contentHeight / metrics.listRowHeight);
|
||||
}
|
||||
|
||||
void LookedUpWordsActivity::renderScreen() {
|
||||
void LookedUpWordsActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto orient = renderer.getOrientation();
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -25,6 +21,7 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
std::string cachePath;
|
||||
@@ -35,7 +32,6 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
|
||||
|
||||
std::vector<std::string> words;
|
||||
int selectedIndex = 0;
|
||||
bool updateRequired = false;
|
||||
bool pendingBackFromDef = false;
|
||||
bool pendingExitToReader = false;
|
||||
ButtonNavigator buttonNavigator;
|
||||
@@ -45,11 +41,5 @@ class LookedUpWordsActivity final : public ActivityWithSubactivity {
|
||||
bool ignoreNextConfirmRelease = false;
|
||||
int pendingDeleteIndex = 0;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
int getPageItems() const;
|
||||
void renderScreen();
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user