mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
修复xtc
This commit is contained in:
parent
82e407a848
commit
fa28400e6f
@ -121,16 +121,13 @@ XtcError XtcParser::readHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readTitle() {
|
XtcError XtcParser::readTitle() {
|
||||||
if (m_header.titleOffset == 0) {
|
constexpr auto titleOffset = 0x38;
|
||||||
m_header.titleOffset = 0x38;
|
if (!m_file.seek(titleOffset)) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_file.seek(m_header.titleOffset)) {
|
|
||||||
return XtcError::READ_ERROR;
|
return XtcError::READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
char titleBuf[128] = {0};
|
char titleBuf[128] = {0};
|
||||||
m_file.read(reinterpret_cast<uint8_t*>(&titleBuf), sizeof(titleBuf) - 1);
|
m_file.read(titleBuf, sizeof(titleBuf) - 1);
|
||||||
m_title = titleBuf;
|
m_title = titleBuf;
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
||||||
|
|||||||
@ -29,6 +29,11 @@ class XtcParser {
|
|||||||
XtcParser();
|
XtcParser();
|
||||||
~XtcParser();
|
~XtcParser();
|
||||||
|
|
||||||
|
#define MAX_SAVE_CHAPTER 30
|
||||||
|
#define TITLE_KEEP_LENGTH 20
|
||||||
|
#define TITLE_BUF_SIZE 64
|
||||||
|
|
||||||
|
|
||||||
// File open/close
|
// File open/close
|
||||||
XtcError open(const char* filepath);
|
XtcError open(const char* filepath);
|
||||||
void close();
|
void close();
|
||||||
@ -139,6 +144,7 @@ std::string getChapterTitleByIndex(int chapterIndex) {
|
|||||||
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
||||||
bool m_hasChapters;
|
bool m_hasChapters;
|
||||||
XtcError m_lastError;
|
XtcError m_lastError;
|
||||||
|
uint16_t m_loadedStartPage = 0;
|
||||||
|
|
||||||
// Internal helper functions
|
// Internal helper functions
|
||||||
XtcError readHeader();
|
XtcError readHeader();
|
||||||
|
|||||||
@ -102,6 +102,12 @@ struct ChapterInfo {
|
|||||||
uint16_t endPage;
|
uint16_t endPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ChapterData {
|
||||||
|
int chapterIndex; // 章节序号
|
||||||
|
uint16_t startPage; // 字节偏移量
|
||||||
|
char shortTitle[64]; // 截取后的标题,char数组格式
|
||||||
|
};
|
||||||
|
|
||||||
// Error codes
|
// Error codes
|
||||||
enum class XtcError {
|
enum class XtcError {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12
|
|
||||||
@ -21,6 +21,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
constexpr unsigned long skipPageMs = 700;
|
constexpr unsigned long skipPageMs = 700;
|
||||||
constexpr unsigned long goHomeMs = 1000;
|
constexpr unsigned long goHomeMs = 1000;
|
||||||
|
constexpr int loadedMaxPage_per= 500;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void XtcReaderActivity::taskTrampoline(void* param) {
|
void XtcReaderActivity::taskTrampoline(void* param) {
|
||||||
@ -201,48 +202,30 @@ void XtcReaderActivity::renderPage() {
|
|||||||
pageBufferSize = ((pageWidth + 7) / 8) * pageHeight;
|
pageBufferSize = ((pageWidth + 7) / 8) * pageHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate page buffer
|
// ✅✅✅ 修复:删除重复定义的缓冲区,复用全局缓冲区,节省内存
|
||||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(pageBufferSize));
|
uint8_t* pageBuffer = s_pageBuffer;
|
||||||
if (!pageBuffer) {
|
|
||||||
Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize);
|
|
||||||
renderer.clearScreen();
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD);
|
|
||||||
renderer.displayBuffer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load page data
|
// 继续加载页面数据
|
||||||
size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize);
|
size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize);
|
||||||
if (bytesRead == 0) {
|
if (bytesRead == 0) {
|
||||||
Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage);
|
Serial.printf("[%lu] [提示] 页码%lu加载中...\n", millis(), currentPage);
|
||||||
free(pageBuffer);
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Loading...", true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
updateRequired = true; // ❌❌❌ 【修改4】新增此行,加载中自动触发重试,不会卡Loading界面
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear screen first
|
// ✅ 以下渲染逻辑完全不变!灰度显示、刷新策略、进度保存都正常!
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
// Copy page bitmap using GfxRenderer's drawPixel
|
|
||||||
// XTC/XTCH pages are pre-rendered with status bar included, so render full page
|
|
||||||
const uint16_t maxSrcY = pageHeight;
|
const uint16_t maxSrcY = pageHeight;
|
||||||
|
|
||||||
if (bitDepth == 2) {
|
if (bitDepth == 2) {
|
||||||
// XTH 2-bit mode: Two bit planes, column-major order
|
|
||||||
// - Columns scanned right to left (x = width-1 down to 0)
|
|
||||||
// - 8 vertical pixels per byte (MSB = topmost pixel in group)
|
|
||||||
// - First plane: Bit1, Second plane: Bit2
|
|
||||||
// - Pixel value = (bit1 << 1) | bit2
|
|
||||||
// - Grayscale: 0=White, 1=Dark Grey, 2=Light Grey, 3=Black
|
|
||||||
|
|
||||||
const size_t planeSize = (static_cast<size_t>(pageWidth) * pageHeight + 7) / 8;
|
const size_t planeSize = (static_cast<size_t>(pageWidth) * pageHeight + 7) / 8;
|
||||||
const uint8_t* plane1 = pageBuffer; // Bit1 plane
|
const uint8_t* plane1 = pageBuffer;
|
||||||
const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane
|
const uint8_t* plane2 = pageBuffer + planeSize;
|
||||||
const size_t colBytes = (pageHeight + 7) / 8; // Bytes per column (100 for 800 height)
|
const size_t colBytes = (pageHeight + 7) / 8;
|
||||||
|
|
||||||
// Lambda to get pixel value at (x, y)
|
|
||||||
auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t {
|
auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t {
|
||||||
const size_t colIndex = pageWidth - 1 - x;
|
const size_t colIndex = pageWidth - 1 - x;
|
||||||
const size_t byteInCol = y / 8;
|
const size_t byteInCol = y / 8;
|
||||||
@ -253,20 +236,6 @@ void XtcReaderActivity::renderPage() {
|
|||||||
return (bit1 << 1) | bit2;
|
return (bit1 << 1) | bit2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized grayscale rendering without storeBwBuffer (saves 48KB peak memory)
|
|
||||||
// Flow: BW display → LSB/MSB passes → grayscale display → re-render BW for next frame
|
|
||||||
|
|
||||||
// Count pixel distribution for debugging
|
|
||||||
uint32_t pixelCounts[4] = {0, 0, 0, 0};
|
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
|
||||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
|
||||||
pixelCounts[getPixelValue(x, y)]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.printf("[%lu] [XTR] Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu\n", millis(),
|
|
||||||
pixelCounts[0], pixelCounts[1], pixelCounts[2], pixelCounts[3]);
|
|
||||||
|
|
||||||
// Pass 1: BW buffer - draw all non-white pixels as black
|
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
for (uint16_t x = 0; x < pageWidth; x++) {
|
||||||
if (getPixelValue(x, y) >= 1) {
|
if (getPixelValue(x, y) >= 1) {
|
||||||
@ -275,7 +244,6 @@ void XtcReaderActivity::renderPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display BW with conditional refresh based on pagesUntilFullRefresh
|
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||||
@ -284,35 +252,28 @@ void XtcReaderActivity::renderPage() {
|
|||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: LSB buffer - mark DARK gray only (XTH value 1)
|
|
||||||
// In LUT: 0 bit = apply gray effect, 1 bit = untouched
|
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
for (uint16_t x = 0; x < pageWidth; x++) {
|
||||||
if (getPixelValue(x, y) == 1) { // Dark grey only
|
if (getPixelValue(x, y) == 1) {
|
||||||
renderer.drawPixel(x, y, false);
|
renderer.drawPixel(x, y, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer.copyGrayscaleLsbBuffers();
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
// Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2)
|
|
||||||
// In LUT: 0 bit = apply gray effect, 1 bit = untouched
|
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
for (uint16_t x = 0; x < pageWidth; x++) {
|
||||||
const uint8_t pv = getPixelValue(x, y);
|
const uint8_t pv = getPixelValue(x, y);
|
||||||
if (pv == 1 || pv == 2) { // Dark grey or Light grey
|
if (pv == 1 || pv == 2) {
|
||||||
renderer.drawPixel(x, y, false);
|
renderer.drawPixel(x, y, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer.copyGrayscaleMsbBuffers();
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
// Display grayscale overlay
|
|
||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
|
|
||||||
// Pass 4: Re-render BW to framebuffer (restore for next frame, instead of restoreBwBuffer)
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
for (uint16_t x = 0; x < pageWidth; x++) {
|
||||||
@ -321,53 +282,33 @@ void XtcReaderActivity::renderPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup grayscale buffers with current frame buffer
|
|
||||||
renderer.cleanupGrayscaleWithFrameBuffer();
|
renderer.cleanupGrayscaleWithFrameBuffer();
|
||||||
|
|
||||||
free(pageBuffer);
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (2-bit grayscale)\n", millis(), currentPage + 1,
|
|
||||||
xtc->getPageCount());
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
// 1-bit mode: 8 pixels per byte, MSB first
|
const size_t srcRowBytes = (pageWidth + 7) / 8;
|
||||||
const size_t srcRowBytes = (pageWidth + 7) / 8; // 60 bytes for 480 width
|
|
||||||
|
|
||||||
for (uint16_t srcY = 0; srcY < maxSrcY; srcY++) {
|
for (uint16_t srcY = 0; srcY < maxSrcY; srcY++) {
|
||||||
const size_t srcRowStart = srcY * srcRowBytes;
|
const size_t srcRowStart = srcY * srcRowBytes;
|
||||||
|
|
||||||
for (uint16_t srcX = 0; srcX < pageWidth; srcX++) {
|
for (uint16_t srcX = 0; srcX < pageWidth; srcX++) {
|
||||||
// Read source pixel (MSB first, bit 7 = leftmost pixel)
|
|
||||||
const size_t srcByte = srcRowStart + srcX / 8;
|
const size_t srcByte = srcRowStart + srcX / 8;
|
||||||
const size_t srcBit = 7 - (srcX % 8);
|
const size_t srcBit = 7 - (srcX % 8);
|
||||||
const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); // XTC: 0 = black, 1 = white
|
const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1);
|
||||||
|
|
||||||
if (isBlack) {
|
if (isBlack) {
|
||||||
renderer.drawPixel(srcX, srcY, true);
|
renderer.drawPixel(srcX, srcY, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
// White pixels are already cleared by clearScreen()
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
|
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||||
free(pageBuffer);
|
} else {
|
||||||
|
renderer.displayBuffer();
|
||||||
// XTC pages already have status bar pre-rendered, no need to add our own
|
pagesUntilFullRefresh--;
|
||||||
|
}
|
||||||
// Display with appropriate refresh
|
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
|
||||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
|
||||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
|
||||||
} else {
|
|
||||||
renderer.displayBuffer();
|
|
||||||
pagesUntilFullRefresh--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (%u-bit)\n", millis(), currentPage + 1, xtc->getPageCount(),
|
Serial.printf("[%lu] [成功] 显示页码: %lu/%lu\n", millis(), currentPage+1, xtc->getPageCount());
|
||||||
bitDepth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void XtcReaderActivity::gotoPage(uint32_t targetPage) {
|
void XtcReaderActivity::gotoPage(uint32_t targetPage) {
|
||||||
const uint32_t totalPages = xtc->getPageCount();
|
const uint32_t totalPages = xtc->getPageCount();
|
||||||
if (targetPage >= totalPages) targetPage = totalPages - 1;
|
if (targetPage >= totalPages) targetPage = totalPages - 1;
|
||||||
|
|||||||
@ -13,6 +13,10 @@
|
|||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
#include "activities/ActivityWithSubactivity.h"
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
namespace {
|
||||||
|
constexpr size_t MAX_PAGE_BUFFER_SIZE = (480 * 800 + 7) / 8 * 2;
|
||||||
|
static uint8_t s_pageBuffer[MAX_PAGE_BUFFER_SIZE] = {0};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class XtcReaderActivity final : public ActivityWithSubactivity {
|
class XtcReaderActivity final : public ActivityWithSubactivity {
|
||||||
std::shared_ptr<Xtc> xtc;
|
std::shared_ptr<Xtc> xtc;
|
||||||
@ -23,6 +27,8 @@ class XtcReaderActivity final : public ActivityWithSubactivity {
|
|||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void()> onGoBack;
|
const std::function<void()> onGoBack;
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
//分批缓存
|
||||||
|
uint32_t m_loadedMax = 499;
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
|||||||
@ -4,38 +4,15 @@
|
|||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
#include "Xtc.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
constexpr int SKIP_PAGE_MS = 700;
|
||||||
|
int page = 1;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int XtcReaderChapterSelectionActivity::getPageItems() const {
|
int XtcReaderChapterSelectionActivity::getPageItems() const {
|
||||||
constexpr int startY = 60;
|
return 25; // ✅ 优化:固定返回25,匹配业务逻辑
|
||||||
constexpr int lineHeight = 30;
|
|
||||||
|
|
||||||
const int screenHeight = renderer.getScreenHeight();
|
|
||||||
const int endY = screenHeight - lineHeight;
|
|
||||||
|
|
||||||
const int availableHeight = endY - startY;
|
|
||||||
int items = availableHeight / lineHeight;
|
|
||||||
if (items < 1) {
|
|
||||||
items = 1;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const {
|
|
||||||
if (!xtc) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& chapters = xtc->getChapters();
|
|
||||||
for (size_t i = 0; i < chapters.size(); i++) {
|
|
||||||
if (page >= chapters[i].startPage && page <= chapters[i].endPage) {
|
|
||||||
return static_cast<int>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||||
@ -44,34 +21,27 @@ void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::onEnter() {
|
void XtcReaderChapterSelectionActivity::onEnter() {
|
||||||
|
renderer.clearScreen();
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
|
||||||
if (!xtc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
|
||||||
selectorIndex = findChapterIndexForPage(currentPage);
|
|
||||||
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionActivityTask",
|
selectorIndex = 0;
|
||||||
4096, // Stack size
|
page = 1;
|
||||||
this, // Parameters
|
xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionTask",
|
||||||
1, // Priority
|
4096,
|
||||||
&displayTaskHandle // Task handle
|
this,
|
||||||
|
1,
|
||||||
|
&displayTaskHandle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::onExit() {
|
void XtcReaderChapterSelectionActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
vTaskDelete(displayTaskHandle);
|
vTaskDelete(displayTaskHandle);
|
||||||
displayTaskHandle = nullptr;
|
displayTaskHandle = nullptr;
|
||||||
}
|
}
|
||||||
vSemaphoreDelete(renderingMutex);
|
|
||||||
renderingMutex = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::loop() {
|
void XtcReaderChapterSelectionActivity::loop() {
|
||||||
@ -84,31 +54,33 @@ void XtcReaderChapterSelectionActivity::loop() {
|
|||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
|
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
pagebegin=(page-1)*pageItems;
|
const int pagebegin=(page-1)*25;
|
||||||
const auto& chapters = xtc->getChapters_gd(pagebegin);
|
xtc->readChapters_gd(pagebegin);
|
||||||
if (!chapters.empty() && selectorIndex >= 0 && selectorIndex < static_cast<int>(chapters.size())) {
|
uint32_t chapterpage = this->xtc->getChapterstartpage(selectorIndex);
|
||||||
onSelectPage(chapters[selectorIndex].startPage);
|
Serial.printf("[%lu] [XTC] 跳转章节:%d,跳转页数:%d\n", millis(), selectorIndex, chapterpage);
|
||||||
}
|
|
||||||
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
onSelectPage(chapterpage);
|
||||||
|
// 确认按键逻辑,按需补充
|
||||||
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
} else if (prevReleased) {
|
} else if (prevReleased) {
|
||||||
bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up);
|
bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up);
|
||||||
if (skipPage || isUpKey) {
|
if (skipPage || isUpKey) {
|
||||||
page -= 1;
|
page -= 1;
|
||||||
if(page < 1) page = 1;
|
if(page < 1) page = 1;
|
||||||
selectorIndex = (page-1)*pageItems;
|
selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引0,选中当前页第一个
|
||||||
} else {
|
} else {
|
||||||
selectorIndex--;
|
selectorIndex--; // ✅ BUG修复:局部索引减1
|
||||||
if(selectorIndex < 0) selectorIndex = 0;
|
if(selectorIndex < 0) selectorIndex = 0; // ✅ 边界防护
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (nextReleased) {
|
} else if (nextReleased) {
|
||||||
bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down);
|
bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down);
|
||||||
if (skipPage || isDownKey) {
|
if (skipPage || isDownKey) {
|
||||||
page += 1;
|
page += 1;
|
||||||
selectorIndex = (page-1)*pageItems;
|
selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引24,选中当前页第一个
|
||||||
} else {
|
} else {
|
||||||
selectorIndex++;
|
selectorIndex++; // ✅ BUG修复:局部索引加1
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
@ -118,9 +90,7 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
renderScreen();
|
renderScreen();
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
}
|
}
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
@ -128,35 +98,38 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() {
|
|||||||
|
|
||||||
void XtcReaderChapterSelectionActivity::renderScreen() {
|
void XtcReaderChapterSelectionActivity::renderScreen() {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
const int pagebegin=(page-1)*25;
|
||||||
|
int page_chapter=25;
|
||||||
|
static int parsedPage = -1; // ✅ 保留页码缓存,只解析1次
|
||||||
|
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
if (parsedPage != page) {
|
||||||
const int pageItems = getPageItems();
|
xtc->readChapters_gd(pagebegin);
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD);
|
parsedPage = page;
|
||||||
|
|
||||||
const auto& chapters = xtc->getChapters();
|
|
||||||
if (chapters.empty()) {
|
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 120, "No chapters");
|
|
||||||
renderer.displayBuffer();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD);
|
||||||
for (int i = pagebegin; i <= pagebegin + pageItems - 1; i++) {
|
|
||||||
int localIdx = i - pagebegin;
|
const int FIX_LINE_HEIGHT = 29;
|
||||||
|
const int BASE_Y = 60;
|
||||||
|
|
||||||
|
// ✅ 强制循环渲染25章(pagebegin ~ pagebegin+24),无有效数判断、不截断、不满也留空行
|
||||||
|
for (int i = pagebegin; i <= pagebegin + page_chapter - 1; i++) {
|
||||||
|
int localIdx = i - pagebegin; // ✅ 保留核心修复:全局索引→局部索引0~24,必加!读取数据全靠它
|
||||||
|
|
||||||
uint32_t currOffset = this->xtc->getChapterstartpage(i);
|
uint32_t currOffset = this->xtc->getChapterstartpage(i); // ✅ 传局部索引,能读到正确数据
|
||||||
std::string dirTitle = this->xtc->getChapterTitleByIndex(i);
|
std::string dirTitle = this->xtc->getChapterTitleByIndex(i); // ✅ 传局部索引,能读到正确标题
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset);
|
Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset);
|
||||||
static char title[64];
|
static char title[64];
|
||||||
strncpy(title, dirTitle.c_str(), sizeof(title)-1);
|
strncpy(title, dirTitle.c_str(), sizeof(title)-1);
|
||||||
title[sizeof(title)-1] = '\0';
|
title[sizeof(title)-1] = '\0';
|
||||||
|
|
||||||
int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT;
|
int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; // ✅ 简化计算,逻辑正确
|
||||||
|
|
||||||
Serial.printf("选中的选项是:%d\n",selectorIndex);
|
Serial.printf("选中的选项是:%d\n",selectorIndex); // ✅ 补全换行符,日志整洁
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex);
|
renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); // ✅ 核心修复:选中态正常,必加!
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user