diff --git a/epdiy-epub/README.md b/epdiy-epub/README.md index 96eb469..88ced50 100644 --- a/epdiy-epub/README.md +++ b/epdiy-epub/README.md @@ -1,71 +1,140 @@ -# epub 电子书阅读器 +# EPUB 电子书阅读器(SF32-OED-EPD) -## 概述 -* 基于[开源EPUB阅读器](https://github.com/atomic14/diy-esp32-epub-reader), 适配到我们的SF32-OED-EPD开发板,支持如下功能: -1. 从内置flash或者T卡读取电子书文件(优先从T卡读取) -2. 提供3个按键(上、下、选择) -3. 支持中文,英文显示。由于中文点阵字库较大,所以去掉了斜体、粗体的字库(见SF32Paper.cpp, `Renderer *SF32Paper::get_renderer() `函数内) +## 项目简介 + +本项目基于 [atomic14/diy-esp32-epub-reader](https://github.com/atomic14/diy-esp32-epub-reader) 进行适配,运行于 SiFli SF32-OED-EPD 系列开发板,用于 EPUB 电子书阅读与低功耗场景。 + +## 当前功能 + +### 电子书与文件系统 + +- 支持从内置文件系统与 TF 卡读取 EPUB(优先使用 TF 卡)。 +- `disk/` 目录可打包示例 EPUB 到镜像中。 + +### 输入与显示 + +- 支持按键与触控双输入。 +- 支持中文/英文显示。 +- 为控制资源占用,默认未启用粗体/斜体中文字库。 + +### UI 页面与状态机 + +当前 UI 状态: + +- `MAIN_PAGE`:主页面 +- `SELECTING_EPUB`:书库页面 +- `SELECTING_TABLE_CONTENTS`:目录页面 +- `READING_EPUB`:阅读页面 +- `SETTINGS_PAGE`:设置页面 +- `WELCOME_PAGE`:欢迎页面 +- `LOW_POWER_PAGE`:低电量页面 +- `CHARGING_PAGE`:充电页面 +- `SHUTDOWN_PAGE`:关机页面 + +默认开机进入 `MAIN_PAGE`。 + +### 主页面 + +主页面包含 3 个入口: + +1. 打开书库 +2. 继续阅读 +3. 进入设置 + +说明: + +- “继续阅读”基于本次运行期间最近一次成功进入阅读的书籍索引。 +- 若当前无可用记录,则显示“无阅读记录”。 + +### 设置页面 + +支持以下设置项: + +1. 触控开关(同步触控硬件上电/下电) +2. 超时策略:`5分钟 / 10分钟 / 30分钟 / 1小时 / 不关机` +3. 全刷周期:`5次 / 10次 / 20次 / 每次(0)` +4. 确认保存并返回主页面 + +### 阅读页覆盖操作层 + +在阅读页触发 `UPGLIDE`(上滑/映射动作)可呼出覆盖操作层,支持: + +- 中心功能在“触控开关”与“全刷周期”间切换 +- 跳页步进:`-5 / -1 / +1 / +5` +- 确认跳转到目标页 +- 进入目录页 +- 返回书库 + +### 书库页与目录页 + +- 书库页:每页 4 项,底部支持“上一页 / 主页面 / 下一页”。 +- 目录页:每页 6 项,底部支持“上一页 / 书库 / 下一页”。 +- 触控采用“先选中再确认”机制以降低误触。 + +### 电量与低功耗 + +- 页面顶部显示电量与充电状态。 +- 低电量进入 `LOW_POWER_PAGE` 并抑制普通用户操作。 +- 充电状态变化可触发页面刷新。 +- 用户无操作达到设置超时后进入 `WELCOME_PAGE`。 +- 主循环默认 5 小时无交互进入 `SHUTDOWN_PAGE`(常量 `TIMEOUT_SHUTDOWN_TIME`)。 + +--- ## 使用指南 + ### 硬件连接 -硬件连接主要是开发板与屏幕的连接,找到相应的卡扣,扣紧(注意查看引脚顺序)就可以连接成功。 -`SF32-OED-6'-EPD_V1.1`和`SF32-OED-6'-EPD_V1.2`硬件上只是布线和个别电阻调整的区别,对软件来说可以用同一个板子配置(即`epdiy-epub\sf32-oed-epd_v11`目录) -### 编译环境要求 -目前epub电子书只验证过[SiFli-ENV 1.1.2](https://docs.sifli.com/projects/sdk/latest/sf32lb52x/quickstart/install/lacey_install.html)模式下使用armclang编译方式,并且Keil的版本要求是5.32。已知在Keil 5.42版本上会有C语法兼容问题, GCC模式下也有兼容问题和空间越界问题,待后续版本更新。 +- 将开发板与墨水屏通过对应连接器连接,注意排线方向。 +- `SF32-OED-EPD_V1.1` 与 `SF32-OED-EPD_V1.2` 可共用 `sf32-oed-epd_base` 软件板级配置。 -#### 程序编译与烧录 -切换到例程project目录,运行scons命令执行编译: -``` -scons --board=sf32-oed-epd_v11 --board_search_path=.. -j8 -``` -运行`build_sf32-oed-epd_hcpu\uart_download.bat`,按提示选择端口即可进行下载: -``` -build_sf32l-oed-epd_v11_hcpu\uart_download.bat -Uart Download -please input the serial port num: 5 (填写相应的端口号) + +### 编译与烧录 + +进入 `epdiy-epub/project` 后执行: + +```bash +scons --board=sf32-oed-epd_v11 --board_search_path=.. -j8 ``` -这里只做简单讲解,详细请查看[编译烧录链接](https://docs.sifli.com/projects/sdk/latest/sf32lb52x/quickstart/build.html) -#### menuconfig配置 -首先查看屏幕的型号,确定menuconfig配置相同型号(默认是`6.0 rect electronic paper display(EPD YZC052_V1.05 1032x758)`),然后再进行编译烧录,查看方式如下图 -切换到例程project目录,运行menuconfig命令执行编译: + +编译完成后下载: + +```bat +build_sf32-oed-epd_v11_hcpu\uart_download.bat ``` -menuconfig --board=sf32-oed-epd_v11 --board_search_path=.. + +按脚本提示输入串口号。 + +参考: +[SiFli 官方编译/烧录文档](https://docs.sifli.com/projects/sdk/latest/sf32lb52x/quickstart/build.html) + +### menuconfig + +```bash +scons --board=sf32-oed-epd_v11 --board_search_path=.. --menuconfig ``` -![alt text](src/assets/menuconfig.png) - -## 阅读指南 -### 按键指南 -1. 主界面阅读EPUB文件选择 - 上下移动选择:按K3键上移光标,按K1键下移光标,浏览EPUB电子书目录列表。 -2. 进入/退出电子书的阅读界面 - 进入书籍:光标选中目标电子书后,按下K2键即可打开。 - 退出返回:阅读中按下K2键,直接退回主界面。 -3. 内容翻阅与设置 - 翻页操作:进入电子书后,按K1键向下翻页,按K3键向上翻页。 -4. 操作逻辑总结 - K1键:用于基础功能向上移动、翻页; - K2键:层级递进操作确认键;以及返回主界面; - K3键:用于基础功能向上移动、翻页。 - -### 触控指南 -* 在屏幕左上角有三个按钮可以实现触摸控制,本质的功能和按键实现功能相同。 -1. 向上的箭头`▲`: 向上移动或者向上翻页。 -2. 向下的箭头`▼`: 向下移动或者向下翻页。 -3. 圆圈`●`: 确认或者返回键。 - - -为了适配低功耗,现版本支持动态开关触控功能:主界面屏幕下方有触控开关选项,将光标移动到下方开关区域后可通过选择操作来进行对触控功能的开启与关闭,需要注意的是,触控默认是关闭的,并且每过一段时间用户无操作进入屏保状态下后,触控也会自动关闭。 - -### 状态管理指南 -* 系统在5分钟无操作后自动进入屏保界面,此时触控功能关闭,可通过按下任意按键返回阅读 -* 系统在5小时无操作后自动关机,此时可通过长按k3键进行开机 -* 系统检测到低电量时自动进入低电量状态,此时按键与触控均无响应。需插入充电器并且当电量高于阈值时自动进入屏保状态,此时可通过按键进行阅读 - -## 软件框架 -主要的程序框架讲解,具体内容可阅读[开源EPUB阅读器](https://github.com/atomic14/diy-esp32-epub-reader) + + +## 操作说明 + +### 按键动作语义 + +- `UP`:上移 / 上一项 / 向前翻页 +- `DOWN`:下移 / 下一项 / 向后翻页 +- `SELECT`:确认 / 进入 +- `UPGLIDE`:阅读页呼出覆盖操作层 + +### 触控操作 + +- 主页面:点击左右区域切换选项,点击中间区域确认。 +- 书库页:点击书籍项选中,再次点击确认打开。 +- 目录页:点击目录项选中,再次点击确认跳转。 +- 阅读页:左右区域翻页;上滑呼出覆盖层;覆盖层内执行跳页/目录/返回书库。 + +--- + +## 目录结构 ``` -epdiy-epub │ ├──disk # 内置flash,存放少量EPUB电子书文件 ├──lib # 存放项目依赖的第三方库或自定义基础库 @@ -83,7 +152,7 @@ epdiy-epub | ├──project # 编译脚本为项目编译、调试、部署提供工具链支持 | -├──sf32-oed-epd_v11 # SF32-OED-6'-EPD_V1.1`和`SF32-OED-6'-EPD_V1.2`开发板公用的相关配置文件 +├──sf32-oed-epd_v11和sf32-oed-epd_v12 # `SF32-OED-EPD_V1.1`和`SF32-OED-EPD_V1.2`开发板公用的相关配置文件 ├──sf32-oed-epd_v12_spi # `SF32-OED-6'-EPD_V1.2`开发板搭配SPI墨水屏的相关配置文件 | ├──src # 项目核心源码目录,实现阅读器业务逻辑 @@ -96,49 +165,61 @@ epdiy-epub └──main.cpp # 程序入口(main 函数),初始化硬件、加载库、启动阅读器主逻辑(如打开 EPUB 文件、进入阅读界面 ) ``` -## 开发指南 -### 添加EPUB电子书 -* 添加少量EPUB电子书文件,只需要在`epdiy-epub\disk`目录下进行添加相应文件,注意这里存放的空间很小, -* 增加大量EPUB电子书文件,主要通过读取TF卡内容,实现大量文件读取功能 - -### 更换屏幕 -* 已有的三个屏幕型号分别是`6.0 rect electronic paper display(EPD R7D005_-1.30 1448x1072)`、`6.0 rect electronic paper display(EPD YZC052_V1.05 1032x758)`、`6.7 rect electronic paper display(TiamMa TE067XJHE01 V1.0 1920x960)` -可以通过menuconfig配置选择不同的屏幕
-#### 添加新的屏幕屏驱 -1、复制`src\board\display\epd_configs_yzc085.c`以新的屏幕名称命名这个文件
-2、根据屏幕波形文档,将波形数据转成一个数组例如全刷波形数组`static const uint8_t yzc085_wave_forms_full[32][256] = {}`和局刷波形数组`static const uint8_t yzc085_wave_forms_partial[12][256] = {}`
-3、对应屏驱文档,修改下列函数 -* 1、如果波形多个温度,可通过波形表生成不同温度的多个二维波形数组,可参考如下代码,通过温度控制不同刷新不同的数据 +--- + +## 二次开发 + +### 添加 EPUB + +- 少量样书:放入 `disk/`,随文件系统镜像打包。 +- 大量书籍:建议使用 TF 卡。 + +#### 添加新的屏幕屏驱 + +1. 复制已有屏驱配置文件并改名(建议以 `src/boards/display_dbi/epd_configs_opm060d.c` 为模板,按新屏型号命名)。 +2. 根据屏幕波形文档,将波形数据转换为数组,例如: + - 全刷波形:`static const uint8_t xxx_wave_forms_full[32][256] = {}` + - 局刷波形:`static const uint8_t xxx_wave_forms_partial[12][256] = {}` +3. 按屏驱文档修改关键函数(波形选择、LUT 转换、时序频率、VCOM)。 + + * 3.1多温区波形选择(可选) + + 如果波形按温度分段,可组织为多组二维数组,通过温度选择对应波形: + ```c // 定义波形表条目结构体 typedef struct { - int min_temp; - int max_temp; - uint32_t frame_count; - const uint8_t (*wave_table)[256]; + int min_temp; + int max_temp; + uint32_t frame_count; + const uint8_t (*wave_table)[256]; } WaveTableEntry; + // 原始波形数据 static const uint8_t te067xjhe_wave_full_0_5[45][256] = {}; static const uint8_t te067xjhe_wave_full_5_10[45][256] = {}; -//... +// ... static const uint8_t te067xjhe_wave_full_50_100[45][256] = {}; + // 全刷波形表 - 按温度区间组织 static const WaveTableEntry te067xjhe_wave_forms_full[] = { {0, 5, 45, &te067xjhe_wave_full_0_5[0]}, {5, 10, 45, &te067xjhe_wave_full_5_10[0]}, - //... - {50, 100, 45, &te067xjhe_wave_full_50_plus[0]}, // The range above 50 degrees + // ... + {50, 100, 45, &te067xjhe_wave_full_50_100[0]}, }; + static const uint8_t *p_current_wave_from = NULL; + uint32_t epd_wave_table_get_frames(int temperature, EpdDrawMode mode) { const WaveTableEntry *wave_table; size_t table_size; - + wave_table = te067xjhe_wave_forms_full; table_size = sizeof(te067xjhe_wave_forms_full) / sizeof(WaveTableEntry); - // Find the interval corresponding to the temperature + // 查找与温度匹配的区间 for (size_t i = 0; i < table_size; i++) { if (temperature >= wave_table[i].min_temp && temperature < wave_table[i].max_temp) { p_current_wave_from = (const uint8_t *)(*wave_table[i].wave_table); @@ -146,47 +227,58 @@ uint32_t epd_wave_table_get_frames(int temperature, EpdDrawMode mode) } } + // 默认回退到第一组 p_current_wave_from = (const uint8_t *)(*wave_table[0].wave_table); return wave_table[0].frame_count; } - ``` -* 2、将8位波形转换为32位的扩展线性插值(Epic)查找表值,提供给硬件进行波形数据选择,以达到显示效果。 + +* 3.2波形转换为 32 位 Epic LUT + + 将当前帧的 8 位波形转换为硬件使用的 32 位 LUT 值: + ```c void epd_wave_table_fill_lut(uint32_t *p_epic_lut, uint32_t frame_num) { - //从当前选中的波形表(p_current_wave_from)中,取出第 frame_num 帧的波形数据的起始地址,赋值给 p_frame_wave。 - const uint8_t *p_frame_wave = p_current_wave_from + (frame_num * 256);//每一帧的波形数据有 256 个字节([256])。frame_num * 256 就是第 frame_num 帧的起始偏移 + // 每帧波形长度为 256 字节 + const uint8_t *p_frame_wave = p_current_wave_from + (frame_num * 256); - //Convert the 8-bit waveforms to 32-bit epic LUT values + // Convert the 8-bit waveforms to 32-bit epic LUT values for (uint16_t i = 0; i < 256; i++) p_epic_lut[i] = p_frame_wave[i] << 3; } ``` -* 3、根据屏驱文档对照下列参数,调整时序及频率。对照文档获取频率SDCLK->sclk_freq,frame clock->fclk_freq,提供驱动芯片的工作时钟频率参数,根据不同的驱动配置不同的频率。 + +* 3.3调整时序与频率参数 + + 按屏驱规格书配置 `SDCLK -> sclk_freq`、`frame clock -> fclk_freq` 及相关时序参数: + ```c const EPD_TimingConfig *epd_get_timing_config(void) { static const EPD_TimingConfig timing_config = { - .sclk_freq = 24, //像素时钟(列时钟) 单位 MHz - .SDMODE = 0, //SD模式选择 - .LSL = 0, // 行开始信号需要的clock数 - .LBL = 0, // 行起始的空clock数 - .LDL = LCD_HOR_RES_MAX/4, //有效数据的clock数,因为是2bit,8数据线所以需要除以4 - .LEL = 1, //行结束的空clock数 - - .fclk_freq = 83, //行时钟,单位 KHz - .FSL = 1, //起始行信号需要的clock数量 - .FBL = 3, //起始空行的数量 - .FDL = LCD_VER_RES_MAX, //有效数据的行数 - .FEL = 5, //结束空行的数量 + .sclk_freq = 24, // 像素时钟(列时钟),单位 MHz + .SDMODE = 0, // SD 模式选择 + .LSL = 0, // 行开始信号需要的 clock 数 + .LBL = 0, // 行起始的空 clock 数 + .LDL = LCD_HOR_RES_MAX / 4, // 有效数据 clock 数(2bit+8数据线,需除以4) + .LEL = 1, // 行结束空 clock 数 + + .fclk_freq = 83, // 行时钟,单位 KHz + .FSL = 1, // 起始行信号需要的 clock 数 + .FBL = 3, // 起始空行数量 + .FDL = LCD_VER_RES_MAX, // 有效数据行数 + .FEL = 5, // 结束空行数量 }; return &timing_config; } ``` -* 4、提供电子书显示所需的参考电压,确保显示效果稳定。新屏幕的VCOM电压可能不同,需要查阅新屏幕规格书,修改返回值。 +* 3.4设置 VCOM 电压 + + 根据新屏规格书提供电子书显示所需参考电压: + ```c uint16_t epd_get_vcom_voltage(void) { @@ -194,24 +286,77 @@ uint16_t epd_get_vcom_voltage(void) } ``` -4、在Kconfig文件中增加驱动IC的宏定义以及为新的屏幕模组添加menuconfig选项, -打开`epd_reader\epdiy-epub\project\Kconfig.proj`文件,添加新的配置可以参考[添加IC宏和menuconfig选项](https://wiki.sifli.com/tools/%E5%B1%8F%E5%B9%95%E8%B0%83%E8%AF%95/%E6%B7%BB%E5%8A%A0%E5%B1%8F%E5%B9%95%E6%A8%A1%E7%BB%84%EF%BC%883%20%E5%A4%96%E7%BD%AE%EF%BC%89.html)具体内容可参考当前驱动内容进行修改。 +4. 在 `project/Kconfig.proj` 中新增屏幕宏与 `menuconfig` 选项,并将新配置文件加入编译条件。 + +#### 板子必须补充的 `Kconfig.proj` 配置 +* 以`sf32-oed-epd_v12`板子为例 +* 如果你新增了一个屏驱并希望在 `sf32-oed-epd_v12` 上可选可编译,`project/Kconfig.proj` 需要同步添加。建议按以下顺序处理: + +1. 在 `choice "Custom LCD driver"` 下新增你的屏幕选项(建议命名带 `V12`,便于和 `V11` 区分)。 +2. 在该选项中 `select` 对应触控驱动、EPD 类型宏和总线宏(一般为 `BSP_LCDC_USING_EPD_8BIT`)。 +3. 在 `LCD_HOR_RES_MAX` / `LCD_VER_RES_MAX` / `LCD_DPI` 中补上新屏默认值。 +4. 若希望 v12 默认选中新屏,在 `choice` 的 `default ... if BSP_USING_BOARD_SF32_OED_EPD_V12` 中切换为新宏。 + +示例(按你的新屏替换宏名与参数): + +```kconfig +choice + prompt "Custom LCD driver" + default LCD_USING_EPD_YOUR_PANEL_V12 if BSP_USING_BOARD_SF32_OED_EPD_V12 + + config LCD_USING_EPD_YOUR_PANEL_V12 + bool "6.x rect electronic paper display(YourPanel 1234x567) for V1.2 board" + select TSC_USING_GT967 if BSP_USING_TOUCHD + select LCD_USING_OPM060D + select BSP_LCDC_USING_EPD_8BIT +endchoice + +config LCD_HOR_RES_MAX + int + default 1234 if LCD_USING_EPD_YOUR_PANEL_V12 + +config LCD_VER_RES_MAX + int + default 567 if LCD_USING_EPD_YOUR_PANEL_V12 + +config LCD_DPI + int + default 300 if LCD_USING_EPD_YOUR_PANEL_V12 +``` + +完成后建议执行一次: -### 修改字体流程 -先生成指定的unicode码范围数组,然后用这个数组去指定的ttf里面生成字库 -注意:生成的字体的unicdoe编码需要从小到大排列,否则查找会有问题。 +```bash +menuconfig --board=sf32-oed-epd_v12 --board_search_path=.. +``` + +确认新屏选项可见并选中,然后再进行编译。 + +参考: +[SiFli 屏幕模块适配说明](https://wiki.sifli.com/tools/%E5%B1%8F%E5%B9%95%E8%B0%83%E8%AF%95/%E6%B7%BB%E5%8A%A0%E5%B1%8F%E5%B9%95%E6%A8%A1%E7%BB%84%EF%BC%883%20%E5%A4%96%E7%BD%AE%EF%BC%89.html) + +### 3) 字体生成 + +可先生成 Unicode 区间,再由 TTF 生成字库头文件。 + +生成区间示例: + +```bash +python3 get_intervals_from_font.py abc.ttf > interval.h +python3 generate_gb2312_L1_intervals.py +``` + +生成字体示例: + +```bash +python3 fontconvert.py regular_font 15 abc.ttf > ../lib/Fonts/regular_font.h +``` -#### 1. 生成unicode码范围[可选] -当前`fontconvert.py`里面的数组`intervals`存储的是包括常用的英文字符以及GB2312一级字库的unicode码。 +注意:Unicode 区间必须按从小到大排序,否则字符检索会异常。 -##### 获取指定ttf字体里面可用的全部unicode码范围 -1. 获取ttf字体unicode码范围:`python3 get_intervals_from_font.py abc.ttf > interval.h` -2. 将生成的`interval.h` 覆盖 `generate_fonts.sh`里面的数组 `intervals` +--- -##### 指定GB2312一级字库的unicode码范围 -1. `python3 generate_gb2312_L1_intervals.py` 将生成GB2312 一级汉字的unicode码范围 -2. 将生成的数组修改`generate_fonts.sh`里面的数组 `intervals` +## 致谢 -#### 2.根据unicode码范围生成字体 -从abc.ttf生成15号字体,名字为`regular_font`,覆盖../lib/Fonts/regular_font.h, 命令如下 -`python3 fontconvert.py regular_font 15 abc.ttf > ../lib/Fonts/regular_font.h` +- 上游项目:[atomic14/diy-esp32-epub-reader](https://github.com/atomic14/diy-esp32-epub-reader) +- SiFli SDK 与开发文档支持 diff --git a/epdiy-epub/lib/Epub/EpubList/EpubList.cpp b/epdiy-epub/lib/Epub/EpubList/EpubList.cpp index efd6603..81de2fa 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubList.cpp +++ b/epdiy-epub/lib/Epub/EpubList/EpubList.cpp @@ -1,6 +1,6 @@ #include "EpubList.h" #include - +#include "UIRegionsManager.h" #ifndef UNIT_TEST @@ -15,48 +15,36 @@ static const char *TAG = "PUBLIST"; #define PADDING 20 -#define EPUBS_PER_PAGE 5 -#define BOTTOM_AREA_HEIGHT 50 -#define BOTTOM_AREA_ITEM_INDEX -1 +#define EPUBS_PER_PAGE 4 void EpubList::next() { - // 如果当前选中的是最后一个电子书项,则切换到底部区域 - if (state.selected_item == state.num_epubs - 1) - { - state.selected_item = BOTTOM_AREA_ITEM_INDEX; - } - else if (state.selected_item == BOTTOM_AREA_ITEM_INDEX) - { - // 如果当前已在底部区域,则回到第一个电子书项 + if (state.num_epubs == 0) return; + // 正常切换到下一个电子书项 + if (state.selected_item >= 0 && state.selected_item < state.num_epubs - 1) + state.selected_item++; + else state.selected_item = 0; - } - else - { - // 正常切换到下一个电子书项 - state.selected_item = (state.selected_item + 1) % state.num_epubs; - } } void EpubList::prev() { - if (state.selected_item == 0) - { - // 如果当前是第一个电子书项,则切换到底部区域 - state.selected_item = BOTTOM_AREA_ITEM_INDEX; - } - else if (state.selected_item == BOTTOM_AREA_ITEM_INDEX) - { - // 如果当前已在底部区域,则切换到最后一个电子书项 - state.selected_item = state.num_epubs > 0 ? state.num_epubs - 1 : 0; - } - else - { - // 正常切换到上一个电子书项 - state.selected_item = (state.selected_item - 1 + state.num_epubs) % state.num_epubs; - } + if (state.num_epubs == 0) return; + if (state.selected_item <= 0) + state.selected_item = state.num_epubs - 1; + else + state.selected_item--; } +void EpubList::switch_book(int target_index) +{ + if (state.num_epubs == 0) return; + if (target_index < 0 || target_index >= state.num_epubs) + return; + state.selected_item = target_index; +} + + bool EpubList::load(const char *path) { if (state.is_loaded) @@ -146,11 +134,14 @@ bool EpubList::load(const char *path) void EpubList::render() { + //clear_areas(); ulog_d(TAG, "Rendering EPUB list"); // what page are we on? int current_page = state.selected_item / EPUBS_PER_PAGE; - // 计算单元格高度,减去底部区域的高度 - int cell_height = (renderer->get_page_height() - BOTTOM_AREA_HEIGHT) / EPUBS_PER_PAGE; + // 计算单元格高度,并为底部按钮预留区域与底部间距 + const int bottom_area_height = 100; // 底部三按钮区域高度 + const int bottom_margin = 30; // 与屏幕底部的间距 + int cell_height = (renderer->get_page_height() - bottom_area_height - bottom_margin) / EPUBS_PER_PAGE; ulog_d(TAG, "Cell height is %d", cell_height); int start_index = current_page * EPUBS_PER_PAGE; int ypos = 0; @@ -167,6 +158,7 @@ void EpubList::render() } for (int i = start_index; i < start_index + EPUBS_PER_PAGE && i < state.num_epubs; i++) { + // do we need to draw a new page of items? if (current_page != state.previous_rendered_page) { @@ -201,6 +193,16 @@ void EpubList::render() title_block->render(renderer, i, text_xpos, text_ypos + y_offset); y_offset += renderer->get_line_height(); } + // 计算整体区域范围 + int area_start_x = image_xpos; + int area_start_y = image_ypos; + int area_end_x = std::max(image_xpos + image_width, text_xpos + text_width); + int area_end_y = std::max(image_ypos + image_height, text_ypos + title_height); + if((i%4)<4) + { + static_add_area(area_start_x, area_start_y, area_end_x - area_start_x, area_end_y - area_start_y, (i%4)); + } + delete title_block; delete epub; } @@ -212,92 +214,72 @@ void EpubList::render() renderer->draw_rect(i, ypos + PADDING / 2 + i, renderer->get_page_width() - 2 * i, cell_height - PADDING - 2 * i, 255); } } - // draw the selection box around the current selection - if (state.selected_item == i) + // 当不处于底部按钮选择模式时,绘制列表高亮 + // 若处于底部模式,则擦除列表高亮,避免同时双高亮 + if (!m_bottom_mode) { - for (int i = 0; i < 5; i++) + if (state.selected_item == i) { - renderer->draw_rect(i, ypos + PADDING / 2 + i, renderer->get_page_width() - 2 * i, cell_height - PADDING - 2 * i, 0); + for (int line = 0; line < 5; line++) + { + renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 0); + } + } + } + else + { + if (state.selected_item == i) + { + // 擦除之前的黑色高亮边框 + for (int line = 0; line < 5; line++) + { + renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 255); + } } } ypos += cell_height; } state.previous_selected_item = state.selected_item; state.previous_rendered_page = current_page; - - // touch 开关底部区域 - - int screen_height = renderer->get_page_height(); - int bottom_area_y = screen_height - BOTTOM_AREA_HEIGHT - 11; - - - int original_width = renderer->get_page_width() - 2 * PADDING; - int rect_width = original_width * 2 / 3; - int rect_x = PADDING + (original_width - rect_width) / 2; + // 绘制底部三按钮区域 + int page_w = renderer->get_page_width(); + int page_h = renderer->get_page_height(); + int area_y = page_h - bottom_area_height - bottom_margin; + // 背景 + renderer->fill_rect(0, area_y, page_w, bottom_area_height, 255); + // 三个等宽按钮 + int btn_gap = 10; + int btn_w = (page_w - btn_gap * 4) / 3; + int btn_h = 80; + int btn_y = area_y + (bottom_area_height - btn_h) / 2; + int btn_x0 = btn_gap; // 上一页 + int btn_x1 = btn_gap * 2 + btn_w; // 主页面 + int btn_x2 = btn_gap * 3 + btn_w * 2; // 下一页 - int rect_height = BOTTOM_AREA_HEIGHT; - - if (bottom_area_y < 0) - { - bottom_area_y = 5; - rect_height = BOTTOM_AREA_HEIGHT; - } - - - renderer->fill_rect(rect_x, bottom_area_y, rect_width, rect_height, 255); - - bool touch_state = touch_controls ? touch_controls->isTouchEnabled() : false; - const char* text = touch_state ? "Touch : On" : "Touch : Off"; - - int text_height = renderer->get_line_height(); - int text_y = bottom_area_y + (rect_height - text_height) / 2; - - if (text_y < bottom_area_y + 2) - { - text_y = bottom_area_y + 2; - } - if (text_y + text_height > bottom_area_y + rect_height - 2) - { - text_y = bottom_area_y + rect_height - text_height - 2; - } - - int text_length = strlen(text); - int estimated_text_width = text_length * 12; - int text_x = rect_x + (rect_width - estimated_text_width) / 2; - - - if (text_x < rect_x + 5) + // 高亮边框:当处于底部模式时,高亮当前选择 + auto draw_button = [&](int x, const char* text, bool selected) { - text_x = rect_x + 5; - } - if (text_x + estimated_text_width > rect_x + rect_width - 5) - { - text_x = rect_x + rect_width - estimated_text_width - 5; - } - - renderer->draw_text(text_x, text_y, text, 0); - - if (state.selected_item == BOTTOM_AREA_ITEM_INDEX) - { - int border_thickness = 3; - for (int i = 0; i < border_thickness; i++) - { - renderer->draw_rect(rect_x + i, bottom_area_y + i, - rect_width - 2 * i, - rect_height - 2 * i, 0); - } - } - else - { - if (state.previous_selected_item == BOTTOM_AREA_ITEM_INDEX) + if (selected) + { + // 加粗描边,表示选中 + for (int i = 0; i < 5; ++i) { - int border_thickness = 3; - for (int i = 0; i < border_thickness; i++) - { - renderer->draw_rect(rect_x + i, bottom_area_y + i, - rect_width - 2 * i, - rect_height - 2 * i, 255); - } + renderer->draw_rect(x + i, btn_y + i, btn_w - 2 * i, btn_h - 2 * i, 0); } - } + } + else + { + // 非选中用细描边 + renderer->draw_rect(x, btn_y, btn_w, btn_h, 80); + } + int t_w = renderer->get_text_width(text); + int t_h = renderer->get_line_height(); + int tx = x + (btn_w - t_w) / 2; + int ty = btn_y + (btn_h - t_h) / 2; + renderer->draw_text(tx, ty, text, false, true); + }; + + draw_button(btn_x0, "上一页", m_bottom_mode && m_bottom_idx == 0); + draw_button(btn_x1, "主页面", m_bottom_mode && m_bottom_idx == 1); + draw_button(btn_x2, "下一页", m_bottom_mode && m_bottom_idx == 2); } \ No newline at end of file diff --git a/epdiy-epub/lib/Epub/EpubList/EpubList.h b/epdiy-epub/lib/Epub/EpubList/EpubList.h index c70a727..f73eec5 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubList.h +++ b/epdiy-epub/lib/Epub/EpubList/EpubList.h @@ -31,13 +31,21 @@ class EpubList EpubListState &state; bool m_needs_redraw = false; TouchControls* touch_controls = nullptr; + // 底部按钮选择状态:是否处于底部按钮选择模式与当前索引(0:上一页,1:主页面,2:下一页) + bool m_bottom_mode = false; + int m_bottom_idx = 1; public: EpubList(Renderer *renderer, EpubListState &state) : renderer(renderer), state(state){}; void setTouchControls(TouchControls* controls) { touch_controls = controls; } + // 设置底部按钮选择状态 + void set_bottom_selection(bool enabled, int idx) { m_bottom_mode = enabled; m_bottom_idx = idx; } + bool is_bottom_mode() const { return m_bottom_mode; } + int bottom_index() const { return m_bottom_idx; } ~EpubList() {} bool load(const char *path); void set_needs_redraw() { m_needs_redraw = true; } void next(); void prev(); void render(); + void switch_book(int target_index); }; \ No newline at end of file diff --git a/epdiy-epub/lib/Epub/EpubList/EpubReader.cpp b/epdiy-epub/lib/Epub/EpubList/EpubReader.cpp index 5be05ac..201478f 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubReader.cpp +++ b/epdiy-epub/lib/Epub/EpubList/EpubReader.cpp @@ -12,6 +12,7 @@ #include "../RubbishHtmlParser/RubbishHtmlParser.h" #include "../Renderer/Renderer.h" #include "epub_mem.h" +#include "epub_screen.h" static const char *TAG = "EREADER"; extern "C" rt_uint32_t heap_free_size(void); @@ -57,6 +58,9 @@ void EpubReader::parse_and_layout_current_section() parser = new RubbishHtmlParser(html, strlen(html), base_path); epub_mem_free(html); ulog_d(TAG, "After parse: %d", heap_free_size()); + // 为底部章节进度预留高度 + int reserved_bottom = renderer->get_line_height() + 10; + renderer->set_margin_bottom(reserved_bottom); parser->layout(renderer, epub); ulog_d(TAG, "After layout: %d", heap_free_size()); state.pages_in_current_section = parser->get_page_count(); @@ -99,13 +103,263 @@ void EpubReader::render() { parse_and_layout_current_section(); } + // 确保覆盖层目标页初始与当前页同步(1-based) + if (overlay_active && overlay_target_page < 1) + { + overlay_set_target_page(state.current_page + 1); + } ulog_d(TAG, "rendering page %d of %d", state.current_page, parser->get_page_count()); parser->render_page(state.current_page, renderer, epub); ulog_d(TAG, "rendered page %d of %d", state.current_page, parser->get_page_count()); ulog_d(TAG, "after render: %d", heap_free_size()); + // 章节进度 + if (state.pages_in_current_section > 0) + { + char buf[32]; + rt_snprintf(buf, sizeof(buf), "%d页/%d页", state.current_page + 1, state.pages_in_current_section); + int page_w = renderer->get_page_width(); + int page_h = renderer->get_page_height(); + int text_w = renderer->get_text_width(buf); + int text_h = renderer->get_line_height(); + int x = (page_w - text_w) / 2; + int reserved_bottom = renderer->get_line_height() + 4; + const int progress_up = 6; // 上抬 + int y = page_h - text_h - 10 + reserved_bottom - progress_up; + renderer->draw_text(x, y, buf, false, true); + } + // 绘制半屏覆盖操作层 + if (overlay_active) + { + render_overlay(); + } } void EpubReader::set_state_section(uint16_t current_section) { ulog_i(TAG, "go to section:%d", current_section); state.current_section = current_section; +} + +void EpubReader::render_overlay() +{ + int page_w = renderer->get_page_width(); + int page_h = renderer->get_page_height(); + int area_y = (page_h * 2) / 3; // 覆盖下方 1/3 屏幕 + int area_h = page_h - area_y; + + renderer->fill_rect(0, area_y, page_w, area_h, 240); + + // 三行布局:3,5,3 + const int rows = 3; + const int cols[rows] = {3, 5, 3}; + const int gap_h = 20; // 行间距 + const int gap_w = 10; + const int row_h = 80; // 每行高度 + // 纵向居中放置三行 + int content_h = rows * row_h + (rows + 1) * gap_h; + int y0 = area_y + (area_h - content_h) / 2; + if (y0 < area_y + 4) y0 = area_y + 4; + + int index = 0; + auto fill_label = [&](int idx, char *label, size_t cap) { + switch (idx) + { + case 0: rt_snprintf(label, cap, "<"); break; + case 1: + { + if (overlay_center_mode == CENTER_TOUCH) + { + rt_snprintf(label, cap, "触摸开关:%s", overlay_touch_enabled ? "开" : "关"); + } + else + { + int v = overlay_get_full_refresh_value(); + if (v == 0) + rt_snprintf(label, cap, "全刷周期:每次"); + else + rt_snprintf(label, cap, "全刷周期:%d次", v); + } + break; + } + case 2: rt_snprintf(label, cap, ">"); break; + case 3: rt_snprintf(label, cap, "-5"); break; + case 4: rt_snprintf(label, cap, "-1"); break; + // 第六格显示:x/n 页 + case 5: + { + int total = state.pages_in_current_section; + if (total <= 0 && parser) total = parser->get_page_count(); + if (total <= 0) total = 1; + rt_snprintf(label, cap, "%d/%d", overlay_target_page, total); + break; + } + case 6: rt_snprintf(label, cap, "+1"); break; + case 7: rt_snprintf(label, cap, "+5"); break; + case 8: rt_snprintf(label, cap, "确认"); break; + case 9: rt_snprintf(label, cap, "目录"); break; + case 10: rt_snprintf(label, cap, "书库"); break; + default: label[0] = '\0'; break; + } + }; + for (int r = 0; r < rows; ++r) + { + int c = cols[r]; + int y = y0 + gap_h + r * (row_h + gap_h); + // 顶部第1行(3列)采用不等宽布局:1/3半宽,2双宽 + if (r == 0) + { + int usable_w = page_w - (c + 1) * gap_w; + // 宽度权重为 1:3:1(约 左20% / 中60% / 右20%) + int w0 = (usable_w * 1) / 5; + int w1 = (usable_w * 3) / 5; + int w2 = usable_w - w0 - w1; + int widths[3] = { w0, w1, w2 }; + int cur_x = gap_w; + for (int i = 0; i < c; ++i) + { + int w = widths[i]; + int x = cur_x; + bool selected = (index == overlay_selected); + if (selected) + { + for (int k = 0; k < 5; ++k) + { + renderer->draw_rect(x + k, y + k, w - 2 * k, row_h - 2 * k, 0); + } + } + else + { + renderer->draw_rect(x, y, w, row_h, 80); + } + char label[32]; + fill_label(index, label, sizeof(label)); + int t_w = renderer->get_text_width(label); + int t_h = renderer->get_line_height(); + int tx = x + (w - t_w) / 2; + int ty = y + (row_h - t_h) / 2; + renderer->draw_text(tx, ty, label, false, true); + index++; + cur_x = x + w + gap_w; + } + } + else + { + int usable_w = page_w - (c + 1) * gap_w; + int btn_w = usable_w / c; + for (int i = 0; i < c; ++i) + { + int x = gap_w + i * (btn_w + gap_w); + bool selected = (index == overlay_selected); + if (selected) + { + for (int k = 0; k < 5; ++k) + { + renderer->draw_rect(x + k, y + k, btn_w - 2 * k, row_h - 2 * k, 0); + } + } + else + { + renderer->draw_rect(x, y, btn_w, row_h, 80); + } + char label[32]; + fill_label(index, label, sizeof(label)); + int t_w = renderer->get_text_width(label); + int t_h = renderer->get_line_height(); + int tx = x + (btn_w - t_w) / 2; + int ty = y + (row_h - t_h) / 2; + renderer->draw_text(tx, ty, label, false, true); + index++; + } + } + } +} + +void EpubReader::overlay_move_left() +{ + if (!overlay_active) return; + overlay_selected = (overlay_selected - 1 + 11) % 11; +} + +void EpubReader::overlay_move_right() +{ + if (!overlay_active) return; + overlay_selected = (overlay_selected + 1) % 11; +} + +void EpubReader::jump_pages(int delta) +{ + if (delta == 0) return; + if (!parser) //没解析的情况下 则解析当前节 + { + parse_and_layout_current_section(); + } + int spine_count = epub ? epub->get_spine_items_count() : 0; //获取章节总数 + if (spine_count <= 0) return; + + //检查是不是第一页 + auto at_book_start = [&]() -> bool + { + return state.current_section == 0 && state.current_page == 0; + }; + //检查是不是最后一页 + auto at_book_end = [&]() -> bool + { + if (!parser) return false; + return (state.current_section == spine_count - 1) && (state.current_page >= state.pages_in_current_section - 1); + }; + // 开始实现页面跳转 + if (delta > 0) + { + for (int i = 0; i < delta; ++i) + { + if (at_book_end()) break; + next(); + // 如果跨节,parser 在 next() 时会置空;后续渲染时会自动 parse + if (!parser) + { + parse_and_layout_current_section(); + } + } + } + else // delta < 0 + { + for (int i = 0; i < -delta; ++i) + { + if (at_book_start()) break; + prev(); + if (!parser) + { + //空就解析 + parse_and_layout_current_section(); + } + } + } +} + +void EpubReader::overlay_cycle_full_refresh() +{ + screen_cycle_full_refresh_period(true); +} + +int EpubReader::overlay_get_full_refresh_value() const +{ + return screen_get_full_refresh_period(); +} + +void EpubReader::overlay_set_target_page(int p) +{ + if (p < 1) p = 1; + int maxp = state.pages_in_current_section; + if (maxp <= 0 && parser) + { + maxp = parser->get_page_count(); + } + if (maxp <= 0) maxp = 1; + if (p > maxp) p = maxp; + overlay_target_page = p; +} + +void EpubReader::overlay_adjust_target_page(int d) +{ + int p = overlay_target_page + d; + overlay_set_target_page(p); } \ No newline at end of file diff --git a/epdiy-epub/lib/Epub/EpubList/EpubReader.h b/epdiy-epub/lib/Epub/EpubList/EpubReader.h index 5d46081..d65ef72 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubReader.h +++ b/epdiy-epub/lib/Epub/EpubList/EpubReader.h @@ -13,8 +13,22 @@ class EpubReader Epub *epub = nullptr; Renderer *renderer = nullptr; RubbishHtmlParser *parser = nullptr; + // 阅读页半屏覆盖操作层状态 + bool overlay_active = false; + int overlay_selected = 0; // 0..10,共11个 + int overlay_jump_acc = 0; // 覆盖层累积跳页值(可为负) + // 覆盖层目标页(当前章节内的页,1-based) + int overlay_target_page = 1; + // 覆盖层中心属性模式:触控开关 或 全刷周期 + enum OverlayCenterMode { CENTER_TOUCH = 0, CENTER_FULL_REFRESH = 1 }; + OverlayCenterMode overlay_center_mode = CENTER_TOUCH; + // 触控开关当前状态(由上层同步) + bool overlay_touch_enabled = false; + // 全刷周期索引:0->5, 1->10, 2->20, 3->每次(0) + int overlay_fr_idx = 0; void parse_and_layout_current_section(); + void render_overlay(); public: EpubReader(EpubListItem &state, Renderer *renderer) : state(state), renderer(renderer){}; @@ -22,6 +36,29 @@ class EpubReader bool load(); void next(); void prev(); + void jump_pages(int delta); void render(); void set_state_section(uint16_t current_section); + // 覆盖层控制 + void start_overlay() { overlay_active = true; overlay_selected = 0; overlay_jump_acc = 0; overlay_target_page = state.current_page + 1; } + void stop_overlay() { overlay_active = false; } + bool is_overlay_active() const { return overlay_active; } + void overlay_move_left(); + void overlay_move_right(); + int get_overlay_selected() const { return overlay_selected; } + // 覆盖层跳页累积控制 + void overlay_reset_jump() { overlay_jump_acc = 0; } + int overlay_get_jump() const { return overlay_jump_acc; } + // 覆盖层目标页控制 + void overlay_set_target_page(int p); + void overlay_adjust_target_page(int d); + int overlay_get_target_page() const { return overlay_target_page; } + // 覆盖层中心属性控制 + void overlay_set_center_mode_touch() { overlay_center_mode = CENTER_TOUCH; } + void overlay_set_center_mode_full_refresh() { overlay_center_mode = CENTER_FULL_REFRESH; } + bool overlay_is_center_touch() const { return overlay_center_mode == CENTER_TOUCH; } + void overlay_set_touch_enabled(bool en) { overlay_touch_enabled = en; } + bool overlay_get_touch_enabled() const { return overlay_touch_enabled; } + void overlay_cycle_full_refresh(); + int overlay_get_full_refresh_value() const; }; \ No newline at end of file diff --git a/epdiy-epub/lib/Epub/EpubList/EpubToc.cpp b/epdiy-epub/lib/Epub/EpubList/EpubToc.cpp index 46ca552..d3eb34c 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubToc.cpp +++ b/epdiy-epub/lib/Epub/EpubList/EpubToc.cpp @@ -1,4 +1,5 @@ #include "EpubToc.h" +#include "UIRegionsManager.h" static const char *TAG = "PUBINDEX"; #define PADDING 14 @@ -31,6 +32,15 @@ void EpubToc::prev() } state.selected_item = (state.selected_item - 1 + epub->get_toc_items_count()) % epub->get_toc_items_count(); } +void EpubToc::switch_book(int target_index) +{ + if (!epub) + { + load(); + } + state.selected_item = target_index % epub->get_toc_items_count(); + +} bool EpubToc::load() { @@ -57,13 +67,17 @@ bool EpubToc::load() // required as we're not rendering thumbnails void EpubToc::render() { + // 初始化固定区域(仅首次调用) ulog_d(TAG, "Rendering EPUB index"); // what page are we on? int current_page = state.selected_item / ITEMS_PER_PAGE; - // show five items per page - int cell_height = renderer->get_page_height() / ITEMS_PER_PAGE; + // 为底部按钮预留区域与底部间距 + const int bottom_area_height = 100; + const int bottom_margin = 30; + int cell_height = (renderer->get_page_height() - bottom_area_height - bottom_margin) / ITEMS_PER_PAGE; int start_index = current_page * ITEMS_PER_PAGE; int ypos = 0; + // starting a fresh page or rendering from scratch? ulog_i(TAG, "Current page is %d, previous page %d, redraw=%d", current_page, state.previous_rendered_page, m_needs_redraw); if (current_page != state.previous_rendered_page || m_needs_redraw) @@ -97,6 +111,16 @@ void EpubToc::render() } // clean up the temporary index block delete title_block; + // 计算整体区域范围并写入 + int area_start_x = 0; + int area_start_y = ypos + PADDING / 2; + int area_end_x = renderer->get_page_width(); + int area_end_y = ypos + cell_height - PADDING / 2; + + if ((i % ITEMS_PER_PAGE) < ITEMS_PER_PAGE) + { + static_add_area(area_start_x, area_start_y, area_end_x - area_start_x, area_end_y - area_start_y, (i % ITEMS_PER_PAGE)); + } } // clear the selection box around the previous selected item if (state.previous_selected_item == i) @@ -106,18 +130,72 @@ void EpubToc::render() renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 255); } } - // draw the selection box around the current selection - if (state.selected_item == i) + // 目录页:仅在非底部按钮模式时显示列表高亮;底部模式下擦除列表高亮 + if (!m_bottom_mode) { - for (int line = 0; line < 3; line++) + if (state.selected_item == i) + { + for (int line = 0; line < 3; line++) + { + renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 0); + } + } + } + else + { + if (state.selected_item == i) { - renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 0); + for (int line = 0; line < 3; line++) + { + renderer->draw_rect(line, ypos + PADDING / 2 + line, renderer->get_page_width() - 2 * line, cell_height - PADDING - 2 * line, 255); + } } } ypos += cell_height; } state.previous_selected_item = state.selected_item; state.previous_rendered_page = current_page; + + // 绘制底部三按钮区域 + int page_w = renderer->get_page_width(); + int page_h = renderer->get_page_height(); + int area_y = page_h - bottom_area_height - bottom_margin; + // 背景 + renderer->fill_rect(0, area_y, page_w, bottom_area_height, 255); + // 三个等宽按钮 + int btn_gap = 10; + int btn_w = (page_w - btn_gap * 4) / 3; // 左右边距各一个gap,再加中间两个gap + int btn_h = 80; + int btn_y = area_y + (bottom_area_height - btn_h) / 2; + int btn_x0 = btn_gap; // 上一页 + int btn_x1 = btn_gap * 2 + btn_w; // 主页面 + int btn_x2 = btn_gap * 3 + btn_w * 2; // 下一页 + + auto draw_button = [&](int x, const char* text, bool selected) + { + if (selected) + { + // 多重描边(黑色),与列表选中效果一致 + for (int i = 0; i < 5; ++i) + { + renderer->draw_rect(x + i, btn_y + i, btn_w - 2 * i, btn_h - 2 * i, 0); + } + } + else + { + // 非选中用细描边(灰色) + renderer->draw_rect(x, btn_y, btn_w, btn_h, 80); + } + int t_w = renderer->get_text_width(text); + int t_h = renderer->get_line_height(); + int tx = x + (btn_w - t_w) / 2; + int ty = btn_y + (btn_h - t_h) / 2; + renderer->draw_text(tx, ty, text, false, true); + }; + + draw_button(btn_x0, "上一页", m_bottom_mode && m_bottom_idx == 0); + draw_button(btn_x1, "书库", m_bottom_mode && m_bottom_idx == 1); + draw_button(btn_x2, "下一页", m_bottom_mode && m_bottom_idx == 2); } uint16_t EpubToc::get_selected_toc() diff --git a/epdiy-epub/lib/Epub/EpubList/EpubToc.h b/epdiy-epub/lib/Epub/EpubList/EpubToc.h index 5ef96a8..4fc09d9 100644 --- a/epdiy-epub/lib/Epub/EpubList/EpubToc.h +++ b/epdiy-epub/lib/Epub/EpubList/EpubToc.h @@ -32,6 +32,9 @@ class EpubToc EpubListItem &selected_epub; EpubTocState &state; bool m_needs_redraw = false; + // 底部按钮选择状态:是否处于底部按钮选择模式与当前索引(0:上一页,1:主页面,2:下一页) + bool m_bottom_mode = false; + int m_bottom_idx = 1; public: EpubToc(EpubListItem &selected_epub, EpubTocState &state, Renderer *renderer) : renderer(renderer), selected_epub(selected_epub), state(state){}; @@ -40,6 +43,11 @@ class EpubToc void next(); void prev(); void render(); + void switch_book(int target_index); void set_needs_redraw() { m_needs_redraw = true; } uint16_t get_selected_toc(); + // 目录项总数 + int get_items_count() const { return epub ? epub->get_toc_items_count() : 0; } + // 设置底部按钮选择状态 + void set_bottom_selection(bool enabled, int idx) { m_bottom_mode = enabled; m_bottom_idx = idx; } }; \ No newline at end of file diff --git a/epdiy-epub/lib/Epub/EpubList/State.h b/epdiy-epub/lib/Epub/EpubList/State.h index d5e5999..fe8c4a0 100644 --- a/epdiy-epub/lib/Epub/EpubList/State.h +++ b/epdiy-epub/lib/Epub/EpubList/State.h @@ -9,11 +9,11 @@ const int MAX_TITLE_SIZE = 100; // nice and simple state that can be persisted easily typedef struct { - char path[MAX_PATH_SIZE]; - char title[MAX_TITLE_SIZE]; - uint16_t current_section; - uint16_t current_page; - uint16_t pages_in_current_section; + char path[MAX_PATH_SIZE];//存储 EPUB 文件的路径 + char title[MAX_TITLE_SIZE];//存储 EPUB 文件的标题 + uint16_t current_section;//记录当前阅读的章节编号(从0开始) + uint16_t current_page;//记录当前章节内的页码(从0开始) + uint16_t pages_in_current_section;//记录当前章节总共有多少页 } EpubListItem; // this is held in the RTC memory @@ -30,7 +30,7 @@ typedef struct // this is held in the RTC memory typedef struct { - int previous_rendered_page; - int previous_selected_item; - int selected_item; + int previous_rendered_page;//记录当前选中的目录项索引 + int previous_selected_item;//记录上一次选中的目录项索引 + int selected_item;//记录上次渲染的页面索引 } EpubTocState; diff --git a/epdiy-epub/project/Kconfig.proj b/epdiy-epub/project/Kconfig.proj index b18b464..1be7b83 100644 --- a/epdiy-epub/project/Kconfig.proj +++ b/epdiy-epub/project/Kconfig.proj @@ -49,12 +49,12 @@ if !BSP_USING_BUILT_LCD bool default n - choice prompt "Custom LCD driver" default LCD_USING_EPD_YZC085_V100 if BSP_USING_BOARD_SF32_OED_EPD_V11 default LCD_USING_EPD_YZC085_V100_V12 if BSP_USING_BOARD_SF32_OED_EPD_V12 + config LCD_USING_EPD_YZC085_V100_V12 bool "6.0 rect electronic paper display(EPD YZC085_V1.05 1032x758) for V1.2 board" select TSC_USING_GT967 if BSP_USING_TOUCHD @@ -105,4 +105,4 @@ if !BSP_USING_BUILT_LCD default 300 if LCD_USING_EPD_YZC085_V100 default 300 if LCD_USING_EPD_YZC085_V100_V12 default 130 if LCD_USING_EPD_OPMO37A3 -endif \ No newline at end of file +endif diff --git a/epdiy-epub/sf32-oed-epd_v12_spi/battery_table.c b/epdiy-epub/sf32-oed-epd_v12_spi/battery_table.c new file mode 100644 index 0000000..962b4fb --- /dev/null +++ b/epdiy-epub/sf32-oed-epd_v12_spi/battery_table.c @@ -0,0 +1,430 @@ +#include +#include "drv_io.h" + +// Discharging curve table +const battery_lookup_point_t discharge_curve_table[] = +{ + { 100, 41950}, + { 99, 41926}, + { 98, 41860}, + { 97, 41791}, + { 96, 41720}, + { 95, 41650}, + { 94, 41580}, + { 93, 41510}, + { 92, 41439}, + { 91, 41370}, + { 90, 41300}, + { 89, 41230}, + { 88, 41160}, + { 87, 41090}, + { 86, 41020}, + { 85, 40949}, + { 84, 40881}, + { 83, 40810}, + { 82, 40740}, + { 81, 40670}, + { 80, 40600}, + { 79, 40530}, + { 78, 40460}, + { 77, 40390}, + { 76, 40322}, + { 75, 40250}, + { 74, 40180}, + { 73, 40110}, + { 72, 40040}, + { 71, 39970}, + { 70, 39900}, + { 69, 39830}, + { 68, 39760}, + { 67, 39690}, + { 66, 39620}, + { 65, 39550}, + { 64, 39480}, + { 63, 39410}, + { 62, 39340}, + { 61, 39270}, + { 60, 39200}, + { 59, 39130}, + { 58, 39060}, + { 57, 38990}, + { 56, 38920}, + { 55, 38850}, + { 54, 38780}, + { 53, 38710}, + { 52, 38640}, + { 51, 38571}, + { 50, 38500}, + { 49, 38430}, + { 48, 38360}, + { 47, 38290}, + { 46, 38220}, + { 45, 38150}, + { 44, 38080}, + { 43, 38011}, + { 42, 37940}, + { 41, 37870}, + { 40, 37800}, + { 39, 37730}, + { 38, 37660}, + { 37, 37590}, + { 36, 37521}, + { 35, 37451}, + { 34, 37380}, + { 33, 37310}, + { 32, 37240}, + { 31, 37170}, + { 30, 37100}, + { 29, 37029}, + { 28, 36960}, + { 27, 36890}, + { 26, 36820}, + { 25, 36750}, + { 24, 36680}, + { 23, 36610}, + { 22, 36540}, + { 21, 36470}, + { 20, 36400}, + { 19, 36330}, + { 18, 36260}, + { 17, 36190}, + { 16, 36120}, + { 15, 36050}, + { 14, 35980}, + { 13, 35910}, + { 12, 35840}, + { 11, 35770}, + { 10, 35700}, + { 9, 35630}, + { 8, 35560}, + { 7, 35490}, + { 6, 35420}, + { 5, 35350}, + { 4, 35280}, + { 3, 35210}, + { 2, 35139}, + { 1, 35070}, + { 0, 35007}, + + +}; +//新换的1k电阻数据 +const battery_lookup_point_t charging_curve_table[] = +{ + { 100, 42180}, + { 99, 42162}, + { 98, 42136}, + { 97, 42109}, + { 96, 42094}, + { 95, 42060}, + { 94, 42039}, + { 93, 42026}, + { 92, 41990}, + { 91, 41965}, + { 90, 41944}, + { 89, 41924}, + { 88, 41892}, + { 87, 41875}, + { 86, 41845}, + { 85, 41822}, + { 84, 41791}, + { 83, 41760}, + { 82, 41750}, + { 81, 41728}, + { 80, 41692}, + { 79, 41654}, + { 78, 41631}, + { 77, 41599}, + { 76, 41580}, + { 75, 41544}, + { 74, 41510}, + { 73, 41495}, + { 72, 41458}, + { 71, 41429}, + { 70, 41410}, + { 69, 41370}, + { 68, 41340}, + { 67, 41310}, + { 66, 41295}, + { 65, 41253}, + { 64, 41206}, + { 63, 41160}, + { 62, 41099}, + { 61, 41029}, + { 60, 40959}, + { 59, 40889}, + { 58, 40819}, + { 57, 40749}, + { 56, 40679}, + { 55, 40609}, + { 54, 40539}, + { 53, 40469}, + { 52, 40399}, + { 51, 40330}, + { 50, 40259}, + { 49, 40195}, + { 48, 40125}, + { 47, 40055}, + { 46, 39985}, + { 45, 39915}, + { 44, 39845}, + { 43, 39779}, + { 42, 39710}, + { 41, 39640}, + { 40, 39570}, + { 39, 39500}, + { 38, 39430}, + { 37, 39360}, + { 36, 39291}, + { 35, 39221}, + { 34, 39153}, + { 33, 39083}, + { 32, 39013}, + { 31, 38943}, + { 30, 38873}, + { 29, 38802}, + { 28, 38740}, + { 27, 38673}, + { 26, 38606}, + { 25, 38540}, + { 24, 38475}, + { 23, 38417}, + { 22, 38340}, + { 21, 38299}, + { 20, 38241}, + { 19, 38185}, + { 18, 38127}, + { 17, 38072}, + { 16, 38012}, + { 15, 37960}, + { 14, 37890}, + { 13, 37820}, + { 12, 37790}, + { 11, 37720}, + { 10, 37650}, + { 9, 37580}, + { 8, 37510}, + { 7, 37440}, + { 6, 37370}, + { 5, 37333}, + { 4, 37263}, + { 3, 37193}, + { 2, 37122}, + { 1, 37053}, + { 0, 36990} +}; +//charging table //原始1k电阻数据 +// const battery_lookup_point_t charging_curve_table[] = +// { +// { 100, 42210}, +// { 99, 42186}, +// { 98, 42176}, +// { 97, 42137}, +// { 96, 42115}, +// { 95, 42094}, +// { 94, 42058}, +// { 93, 42030}, +// { 92, 41997}, +// { 91, 41967}, +// { 90, 41951}, +// { 89, 41911}, +// { 88, 41893}, +// { 87, 41869}, +// { 86, 41825}, +// { 85, 41793}, +// { 84, 41771}, +// { 83, 41740}, +// { 82, 41715}, +// { 81, 41667}, +// { 80, 41640}, +// { 79, 41620}, +// { 78, 41590}, +// { 77, 41563}, +// { 76, 41545}, +// { 75, 41510}, +// { 74, 41467}, +// { 73, 41454}, +// { 72, 41417}, +// { 71, 41401}, +// { 70, 41371}, +// { 69, 41358}, +// { 68, 41316}, +// { 67, 41287}, +// { 66, 41226}, +// { 65, 41151}, +// { 64, 41108}, +// { 63, 41040}, +// { 62, 40965}, +// { 61, 40910}, +// { 60, 40834}, +// { 59, 40770}, +// { 58, 40708}, +// { 57, 40650}, +// { 56, 40583}, +// { 55, 40538}, +// { 54, 40450}, +// { 53, 40380}, +// { 52, 40300}, +// { 51, 40240}, +// { 50, 40165}, +// { 49, 40100}, +// { 48, 40034}, +// { 47, 39962}, +// { 46, 39900}, +// { 45, 39832}, +// { 44, 39764}, +// { 43, 39695}, +// { 42, 39615}, +// { 41, 39556}, +// { 40, 39490}, +// { 39, 39410}, +// { 38, 39340}, +// { 37, 39260}, +// { 36, 39196}, +// { 35, 39119}, +// { 34, 39054}, +// { 33, 38977}, +// { 32, 38924}, +// { 31, 38846}, +// { 30, 38769}, +// { 29, 38711}, +// { 28, 38650}, +// { 27, 38597}, +// { 26, 38540}, +// { 25, 38472}, +// { 24, 38408}, +// { 23, 38354}, +// { 22, 38287}, +// { 21, 38230}, +// { 20, 38186}, +// { 19, 38124}, +// { 18, 38067}, +// { 17, 38015}, +// { 16, 37953}, +// { 15, 37858}, +// { 14, 37801}, +// { 13, 37745}, +// { 12, 37680}, +// { 11, 37610}, +// { 10, 37540}, +// { 9, 37470}, +// { 8, 37400}, +// { 7, 37330}, +// { 6, 37260}, +// { 5, 37190}, +// { 4, 37120}, +// { 3, 37050}, +// { 2, 36979}, +// { 1, 36910}, +// { 0, 36847} +// }; + +// //charging table 6.2k电阻数据 +// const battery_lookup_point_t charging_curve_table[] = +// { +// { 100, 42250}, +// { 99, 42138}, +// { 98, 42125}, +// { 97, 42099}, +// { 96, 42075}, +// { 95, 42014}, +// { 94, 41939}, +// { 93, 41852}, +// { 92, 41783}, +// { 91, 41706}, +// { 90, 41638}, +// { 89, 41568}, +// { 88, 41513}, +// { 87, 41447}, +// { 86, 41405}, +// { 85, 41334}, +// { 84, 41266}, +// { 83, 41195}, +// { 82, 41125}, +// { 81, 41055}, +// { 80, 40985}, +// { 79, 40915}, +// { 78, 40845}, +// { 77, 40775}, +// { 76, 40707}, +// { 75, 40635}, +// { 74, 40565}, +// { 73, 40495}, +// { 72, 40425}, +// { 71, 40355}, +// { 70, 40285}, +// { 69, 40215}, +// { 68, 40145}, +// { 67, 40075}, +// { 66, 40005}, +// { 65, 39935}, +// { 64, 39865}, +// { 63, 39795}, +// { 62, 39725}, +// { 61, 39655}, +// { 60, 39585}, +// { 59, 39515}, +// { 58, 39445}, +// { 57, 39375}, +// { 56, 39305}, +// { 55, 39235}, +// { 54, 39165}, +// { 53, 39095}, +// { 52, 39014}, +// { 51, 38945}, +// { 50, 38874}, +// { 49, 38804}, +// { 48, 38734}, +// { 47, 38664}, +// { 46, 38594}, +// { 45, 38524}, +// { 44, 38454}, +// { 43, 38385}, +// { 42, 38314}, +// { 41, 38244}, +// { 40, 38174}, +// { 39, 38104}, +// { 38, 38034}, +// { 37, 37964}, +// { 36, 37889}, +// { 35, 37819}, +// { 34, 37748}, +// { 33, 37678}, +// { 32, 37608}, +// { 31, 37538}, +// { 30, 37468}, +// { 29, 37397}, +// { 28, 37328}, +// { 27, 37258}, +// { 26, 37188}, +// { 25, 37118}, +// { 24, 37048}, +// { 23, 36978}, +// { 22, 36908}, +// { 21, 36838}, +// { 20, 36768}, +// { 19, 36660}, +// { 18, 36590}, +// { 17, 36520}, +// { 16, 36450}, +// { 15, 36380}, +// { 14, 36310}, +// { 13, 36240}, +// { 12, 36170}, +// { 11, 36100}, +// { 10, 36030}, +// { 9, 35960}, +// { 8, 35890}, +// { 7, 35820}, +// { 6, 35750}, +// { 5, 35680}, +// { 4, 35610}, +// { 3, 35540}, +// { 2, 35469}, +// { 1, 35390}, +// { 0, 35327} +// }; + +const uint32_t discharge_curve_table_size = sizeof(discharge_curve_table) / sizeof(discharge_curve_table[0]); +const uint32_t charging_curve_table_size = sizeof(charging_curve_table) / sizeof(charging_curve_table[0]); + diff --git a/epdiy-epub/sf32-oed-epd_v12_spi/hcpu/board.conf b/epdiy-epub/sf32-oed-epd_v12_spi/hcpu/board.conf index 32d0fc0..f23daf9 100644 --- a/epdiy-epub/sf32-oed-epd_v12_spi/hcpu/board.conf +++ b/epdiy-epub/sf32-oed-epd_v12_spi/hcpu/board.conf @@ -36,15 +36,10 @@ CONFIG_BSP_USING_TOUCHD=y CONFIG_BSP_USING_KEY1=y CONFIG_BSP_KEY1_PIN=34 CONFIG_BSP_KEY1_ACTIVE_HIGH=y +CONFIG_BSP_USING_CHARGER=y CONFIG_PA_USING_AW8155=y CONFIG_AW8155_GPIO_PIN=10 CONFIG_RT_USING_MTD_NOR=y +CONFIG_RT_USING_SPI_MSD=y CONFIG_HCI_ON_LOG=y CONFIG_LOG_ON_CONSOLE=y -CONFIG_USING_ADC_BUTTON=y -CONFIG_ADC_BUTTON_GROUP1_ADC_DEV_CHANNEL=5 -CONFIG_ADC_BUTTON_GROUP1_BUTTON1_VOLT=2992 -CONFIG_ADC_BUTTON_GROUP1_BUTTON1_RANGE=315 -CONFIG_ADC_BUTTON_GROUP1_BUTTON2_VOLT=2345 -CONFIG_ADC_BUTTON_GROUP1_BUTTON2_RANGE=245 - diff --git a/epdiy-epub/src/SConscript b/epdiy-epub/src/SConscript index dd9b659..f19fa4b 100644 --- a/epdiy-epub/src/SConscript +++ b/epdiy-epub/src/SConscript @@ -4,7 +4,9 @@ from building import * import rtconfig cwd = GetCurrentDir() -src = ['main.cpp','epub_mem.c','epub_fonts.c'] + +src = ['main.cpp','UIRegionsManager.cpp','epub_screen.cpp','epub_mem.c','epub_fonts.c'] + src = src + Glob('./assets/*.c') CPPPATH = [cwd] @@ -20,5 +22,4 @@ for d in list: objs = objs + SConscript(os.path.join(cwd, '../waveform/SConscript')) -Return('objs') - +Return('objs') \ No newline at end of file diff --git a/epdiy-epub/src/UIRegionsManager.cpp b/epdiy-epub/src/UIRegionsManager.cpp new file mode 100644 index 0000000..c643a02 --- /dev/null +++ b/epdiy-epub/src/UIRegionsManager.cpp @@ -0,0 +1,44 @@ +#include "UIRegionsManager.h" +#include + +// 2. 定义全局数组和相关变量 +AreaRect g_area_array[MAX_AREAS]; +int g_area_count = 0; + +// 3. 清空区域数组的函数 +void clear_areas() +{ + g_area_count = 0; + memset(g_area_array, 0, sizeof(g_area_array)); +} + +// 4. 添加区域到数组的函数 +bool add_area(int x, int y, int width, int height) +{ + if (g_area_count >= MAX_AREAS) { + return false; // 数组已满 + } + + g_area_array[g_area_count].start_x = x; + g_area_array[g_area_count].start_y = y; + g_area_array[g_area_count].end_x = x + width; + g_area_array[g_area_count].end_y = y + height; + g_area_count++; + + return true; +} + +bool static_add_area(int x, int y, int width, int height ,int index) +{ + + if(index < 0 || index >= MAX_AREAS) { + return false; // 索引超出范围 + } + g_area_array[index].start_x = x; + g_area_array[index].start_y = y; + g_area_array[index].end_x = x + width; + g_area_array[index].end_y = y + height; + + return true; +} + diff --git a/epdiy-epub/src/UIRegionsManager.h b/epdiy-epub/src/UIRegionsManager.h new file mode 100644 index 0000000..9512820 --- /dev/null +++ b/epdiy-epub/src/UIRegionsManager.h @@ -0,0 +1,25 @@ +#ifndef UI_REGIONS_MANAGER_H +#define UI_REGIONS_MANAGER_H + +#include "Actions.h" +#include "Renderer/Renderer.h" +#include "boards/controls/Actions.h" +// 1. 定义区域结构体 +typedef struct { + int start_x; + int start_y; + int end_x; + int end_y; + UIAction action; // 点击该区域时触发的动作 +} AreaRect; + +#define MAX_AREAS 30 // 最大区域数量 +extern AreaRect g_area_array[MAX_AREAS]; +extern int g_area_count; + +void clear_areas(); +bool add_area(int x, int y, int width, int height); //动态添加区域 +bool static_add_area(int x, int y, int width, int height ,int index); //静态添加区域 + + +#endif \ No newline at end of file diff --git a/epdiy-epub/src/boards/controls/Actions.h b/epdiy-epub/src/boards/controls/Actions.h index 785384d..0f38751 100644 --- a/epdiy-epub/src/boards/controls/Actions.h +++ b/epdiy-epub/src/boards/controls/Actions.h @@ -8,6 +8,10 @@ typedef enum UP, DOWN, SELECT, + UPGLIDE, // 长按触发的上滑操作,用于阅读页半屏操作覆盖 + PREV_OPTION, // 上一选项(用于列表选择) + NEXT_OPTION, // 下一选项(用于列表选择) + SELECT_BOX, // 选择框(用于触控选择) LAST_INTERACTION, MSG_DRAW_LOW_POWER_PAGE, MSG_DRAW_CHARGE_PAGE, @@ -15,4 +19,4 @@ typedef enum MSG_UPDATE_CHARGE_STATUS } UIAction; -typedef std::function ActionCallback_t; +typedef std::function ActionCallback_t; \ No newline at end of file diff --git a/epdiy-epub/src/boards/controls/SF32_ButtonControls.cpp b/epdiy-epub/src/boards/controls/SF32_ButtonControls.cpp index 91cb956..458343a 100644 --- a/epdiy-epub/src/boards/controls/SF32_ButtonControls.cpp +++ b/epdiy-epub/src/boards/controls/SF32_ButtonControls.cpp @@ -12,6 +12,11 @@ void button_event_handler(int32_t pin, button_action_t action) { action_cbk(UIAction::UP); } + else if (action == BUTTON_LONG_PRESSED) + { + rt_kprintf("长按 1"); + action_cbk(UIAction::UPGLIDE); + } } #else if (pin == EPD_KEY1) @@ -28,10 +33,7 @@ void button_event_handler(int32_t pin, button_action_t action) { action_cbk(UIAction::SELECT); } - else if (action == BUTTON_LONG_PRESSED) - { - rt_kprintf("长按 1"); - } + } else if (pin == EPD_KEY3) { @@ -39,6 +41,7 @@ void button_event_handler(int32_t pin, button_action_t action) { action_cbk(UIAction::UP); } + } #endif diff --git a/epdiy-epub/src/boards/controls/SF32_TouchControls.cpp b/epdiy-epub/src/boards/controls/SF32_TouchControls.cpp index a2f85c9..81ff267 100644 --- a/epdiy-epub/src/boards/controls/SF32_TouchControls.cpp +++ b/epdiy-epub/src/boards/controls/SF32_TouchControls.cpp @@ -1,16 +1,46 @@ - #include "SF32_TouchControls.h" #include +#include "Actions.h" #include "epd_driver.h" +#include "type.h" +#include "epub_screen.h" +#include "EpubReader.h" + +#include "UIRegionsManager.h" + #ifdef BSP_USING_TOUCHD #include "drv_touch.h" #endif +volatile int g_touch_last_settings_row = -1; +volatile int g_touch_last_settings_dir = 0; +extern int settings_selected_idx; +extern AppUIState ui_state; +extern int book_index; +extern bool library_bottom_mode; +extern int library_bottom_idx; +extern int toc_index; +extern int toc_bottom_idx; +extern bool toc_bottom_mode;//控制目录页面中功能选项的开关 +static int last_clicked_toc_index = -1; // -1 表示没有目录项被选中 +// 添加一个变量来记录上次点击的书籍索引 +static int last_clicked_book_index = -1; // -1 表示没有书籍被选中 +static bool waiting_for_confirmation = false; // 是否正在等待确认 +extern int touch_sel; +extern EpubReader *reader; + +static const int SWIPE_THRESHOLD = 100; // 最小滑动距离阈值 +static bool is_touch_started = false; // 全局或类成员变量 + + +extern AreaRect g_area_array[]; + rt_err_t SF32_TouchControls::tp_rx_indicate(rt_device_t dev, rt_size_t size) { SF32_TouchControls *instance = static_cast (dev->user_data); struct touch_message touch_data; rt_uint16_t x,y; + int i = 0;//用于记录第一次按下的情况 /*Read touch point data*/ rt_device_read(dev, 0, &touch_data, 1); @@ -20,33 +50,371 @@ rt_err_t SF32_TouchControls::tp_rx_indicate(rt_device_t dev, rt_size_t size) y = touch_data.x; - if (TOUCH_EVENT_DOWN == touch_data.event) + // if (TOUCH_EVENT_DOWN == touch_data.event) + // rt_kprintf("Touch down [%d,%d]\r\n", x, y); + // else + // rt_kprintf("Touch up [%d,%d]\r\n", x, y); + if (TOUCH_EVENT_DOWN == touch_data.event) + { rt_kprintf("Touch down [%d,%d]\r\n", x, y); + + // 记录按下时的位置 + if (!is_touch_started) // 只允许第一次触发 + { + instance->touch_start_y = y; + rt_kprintf("Touch start\r\n"); + is_touch_started = true; + } + + instance->is_touch_down = true; + instance->touch_current_y = 0; + // 处理其他触控逻辑... + } else - rt_kprintf("Touch up [%d,%d]\r\n", x, y); - + { + rt_kprintf("Touch up [%d,%d]\r\n", x, y); + instance->touch_current_y = y; + + // 检查是否构成向上滑动手势 + if (instance->is_touch_down) + { + int y_diff = instance->touch_start_y - instance->touch_current_y; // 注意坐标转换 + rt_kprintf("Touch up diff Y: %d\r\n", y_diff); + + if(reader->is_overlay_active() == false) + { + if (y_diff > SWIPE_THRESHOLD) + { + rt_kprintf("Up swipe detected! Diff: %d\n", y_diff); + + // 发送向上滑动动作 + UIAction action = UPGLIDE; + instance->last_action = action; + instance->on_action(action); + + } + } + + + } + + // 清空坐标值,避免下次误用 + instance->touch_start_y = 0; + // 重置触摸状态 + instance->is_touch_down = false; + is_touch_started = false; + } + // 只处理按下事件,忽略释放事件 + if (TOUCH_EVENT_UP == touch_data.event) { + return RT_EOK; + } UIAction action = NONE; // LOG_I("TOUCH", "Received touch event %d,%d", x, y); - if (x >= 10 && x <= 10 + instance->ui_button_width && y < 200) - { - action = DOWN; - instance->renderPressedState(instance->renderer, UP, false); - } - else if (x >= 150 && x <= 150 + instance->ui_button_width && y < 200) - { - action = UP; - instance->renderPressedState(instance->renderer, DOWN, false); - } - else if (x >= 300 && x <= 300 + instance->ui_button_width && y < 200) - { - action = SELECT; - } - else - { + // 主页面底部按键区域:左"<"、右">"、中间文本框 +switch (ui_state) +{ + case MAIN_PAGE://主页面 + + if (x >= g_area_array[0].start_x && x <= g_area_array[0].end_x && y >= g_area_array[0].start_y && y <= g_area_array[0].end_y) + { + rt_kprintf("Touch left < \n"); + action = UP; // 对应 KEY3 功能 + } + else if (x >= g_area_array[1].start_x && x <= g_area_array[1].end_x && y >= g_area_array[1].start_y && y <= g_area_array[1].end_y) + { + action = DOWN; // 对应 KEY1 功能 + rt_kprintf("Touch right > \n"); + } + else if (x >= g_area_array[2].start_x && x <= g_area_array[2].end_x && y >= g_area_array[2].start_y && y <= g_area_array[2].end_y) + { + action = SELECT; // 对应 KEY2 功能 + rt_kprintf("Touch middle SELECT \n"); + } + break; + case SELECTING_EPUB://书库页面 + // 检查是否点击了功能控制按钮 + if(x >= g_area_array[4].start_x && x <= g_area_array[4].end_x && y >= g_area_array[4].start_y && y <= g_area_array[4].end_y) + { + library_bottom_mode = true; + library_bottom_idx = 0; + action = SELECT; + } + else if(x >= g_area_array[5].start_x && x <= g_area_array[5].end_x && y >= g_area_array[5].start_y && y <= g_area_array[5].end_y) + { + library_bottom_mode = true; + library_bottom_idx = 1; + action = SELECT; + } + else if(x >= g_area_array[6].start_x && x <= g_area_array[6].end_x && y >= g_area_array[6].start_y && y <= g_area_array[6].end_y) + { + library_bottom_mode = true; + library_bottom_idx = 2; + action = SELECT; + } + else + { + // 处理书籍选择区域 + int clicked_book_index = -1; + + + if(x >= g_area_array[0].start_x && x <= g_area_array[0].end_x && y >= g_area_array[0].start_y && y <= g_area_array[0].end_y) + { + clicked_book_index = 0; + } + else if(x >= g_area_array[1].start_x && x <= g_area_array[1].end_x && y >= g_area_array[1].start_y && y <= g_area_array[1].end_y) + { + clicked_book_index = 1; + } + else if(x >= g_area_array[2].start_x && x <= g_area_array[2].end_x && y >= g_area_array[2].start_y && y <= g_area_array[2].end_y) + { + clicked_book_index = 2; + } + else if(x >= g_area_array[3].start_x && x <= g_area_array[3].end_x && y >= g_area_array[3].start_y && y <= g_area_array[3].end_y) + { + clicked_book_index = 3; + } + + // 如果点击了书籍区域 + if(clicked_book_index != -1) + { + // 判断是第一次点击还是第二次点击 + if(waiting_for_confirmation && last_clicked_book_index == clicked_book_index) + { + // 第二次点击:执行打开操作 + book_index = clicked_book_index; + library_bottom_mode = false; + rt_kprintf("Open book%d %d\n", book_index, book_index); + action = SELECT; + + // 重置状态 + waiting_for_confirmation = false; + last_clicked_book_index = -1; + } + else + { + // 第一次点击:选择书籍并等待确认 + book_index = clicked_book_index; + last_clicked_book_index = clicked_book_index; + waiting_for_confirmation = true; + action = SELECT_BOX; + rt_kprintf("Select book%d for confirmation, waiting for second click\n", book_index); + } + } + } + break; + case READING_EPUB: //阅读界面 + //翻页操作 + if(x >= 10 && x <= 200 && y >=10 && y <= 1010 && reader->is_overlay_active() == false) + { + action = UP; + } + else if(x >= 550 && x <= 750 && y >=10 && y <= 1010 && reader->is_overlay_active() == false) + { + action = DOWN; + } + //点击正文,推出阅读设置 + if(x >= 10 && x <= 750 && y >=10 && y <= 630 && reader->is_overlay_active()) + { + touch_sel = 8; + action = SELECT; + } + + + //阅读页面控制区域设置 + if(x >= g_area_array[8].start_x && x <= g_area_array[8].end_x && y >= g_area_array[8].start_y && y <= g_area_array[8].end_y && reader->is_overlay_active())//第三层 + { + touch_sel = 8; + action = SELECT; + } + else if(x >= g_area_array[9].start_x && x <= g_area_array[9].end_x && y >= g_area_array[9].start_y && y <= g_area_array[9].end_y && reader->is_overlay_active()) + { + touch_sel = 9; + action = SELECT; + } + else if(x >= g_area_array[10].start_x && x <= g_area_array[10].end_x && y >= g_area_array[10].start_y && y <= g_area_array[10].end_y && reader->is_overlay_active()) + { + touch_sel = 10; + action = SELECT; + } + else if(x >= g_area_array[0].start_x && x <= g_area_array[0].end_x && y >= g_area_array[0].start_y && y <= g_area_array[0].end_y && reader->is_overlay_active())//第一层 + { + touch_sel= 0; + rt_kprintf("Touch middle SELECT %d\n",touch_sel); + action = SELECT; + } + else if(x >= g_area_array[1].start_x && x <= g_area_array[1].end_x && y >= g_area_array[1].start_y && y <= g_area_array[1].end_y && reader->is_overlay_active()) + { + touch_sel= 1; + rt_kprintf("Touch middle SELECT %d\n",touch_sel); + action = SELECT; + } + else if(x >= g_area_array[2].start_x && x <= g_area_array[2].end_x && y >= g_area_array[2].start_y && y <= g_area_array[2].end_y && reader->is_overlay_active()) + { + touch_sel= 2; + rt_kprintf("Touch middle SELECT %d\n",touch_sel); + action = SELECT; + } + else if(x >= g_area_array[3].start_x && x <= g_area_array[3].end_x && y >= g_area_array[3].start_y && y <= g_area_array[3].end_y && reader->is_overlay_active()) + { + touch_sel = 3;//跳转-5页 + action = SELECT; + } + else if(x >= g_area_array[4].start_x && x <= g_area_array[4].end_x && y >= g_area_array[4].start_y && y <= g_area_array[4].end_y && reader->is_overlay_active()) + { + touch_sel = 4;//跳转-1页 + action = SELECT; + } + else if(x >= g_area_array[6].start_x && x <= g_area_array[6].end_x && y >= g_area_array[6].start_y && y <= g_area_array[6].end_y && reader->is_overlay_active()) + { + touch_sel = 6;//跳转+1页 + action = SELECT; + } + else if(x >= g_area_array[7].start_x && x <= g_area_array[7].end_x && y >= g_area_array[7].start_y && y <= g_area_array[7].end_y && reader->is_overlay_active()) + { + touch_sel = 7;//跳转5页 + action = SELECT; + } + + break; + case SELECTING_TABLE_CONTENTS: //目录界面 + if(x >= g_area_array[6].start_x && x <= g_area_array[6].end_x && y >= g_area_array[6].start_y && y <= g_area_array[6].end_y) + { + toc_bottom_mode = true; + toc_bottom_idx = 0; + action = SELECT; + } + else if(x >= g_area_array[7].start_x && x <= g_area_array[7].end_x && y >= g_area_array[7].start_y && y <= g_area_array[7].end_y) + { + toc_bottom_mode = true; + toc_bottom_idx = 1; + action = SELECT; + } + else if(x >= g_area_array[8].start_x && x <= g_area_array[8].end_x && y >= g_area_array[8].start_y && y <= g_area_array[8].end_y) + { + toc_bottom_mode = true; + toc_bottom_idx = 2; + action = SELECT; + } + else// 目录项选择区域 + { + int clicked_toc_index = -1; + + + if(x >= g_area_array[0].start_x && x <= g_area_array[0].end_x && y >= g_area_array[0].start_y && y <= g_area_array[0].end_y) + { + clicked_toc_index = 0; + } + else if(x >= g_area_array[1].start_x && x <= g_area_array[1].end_x && y >= g_area_array[1].start_y && y <= g_area_array[1].end_y) + { + clicked_toc_index = 1; + } + else if(x >= g_area_array[2].start_x && x <= g_area_array[2].end_x && y >= g_area_array[2].start_y && y <= g_area_array[2].end_y) + { + clicked_toc_index = 2; + } + else if(x >= g_area_array[3].start_x && x <= g_area_array[3].end_x && y >= g_area_array[3].start_y && y <= g_area_array[3].end_y) + { + clicked_toc_index = 3; + } + else if(x >= g_area_array[4].start_x && x <= g_area_array[4].end_x && y >= g_area_array[4].start_y && y <= g_area_array[4].end_y) + { + clicked_toc_index = 4; + } + else if(x >= g_area_array[5].start_x && x <= g_area_array[5].end_x && y >= g_area_array[5].start_y && y <= g_area_array[5].end_y) + { + clicked_toc_index = 5; + } + // 如果点击了目录项区域 + if(clicked_toc_index != -1) + { + // 判断是第一次点击还是第二次点击 + if(waiting_for_confirmation && last_clicked_toc_index == clicked_toc_index) + { + // 第二次点击:执行打开操作 + toc_index = clicked_toc_index; + library_bottom_mode = false; + rt_kprintf("Open book%d %d\n", toc_index, toc_index); + action = SELECT; + + // 重置状态 + waiting_for_confirmation = false; + last_clicked_toc_index = -1; + } + else + { + // 第一次点击:选择目录项并等待确认 + toc_index = clicked_toc_index; + last_clicked_toc_index = clicked_toc_index; + waiting_for_confirmation = true; + action = SELECT_BOX; + } + } + } + + break; + case SETTINGS_PAGE: // 设置页面 + // 设置页面每行左右箭头触控区域(与设置页布局一致) + + if (x >= g_area_array[2].start_x && x <= g_area_array[2].end_x && y >= g_area_array[2].start_y && y <= g_area_array[2].end_y) + { + settings_selected_idx = SET_TOUCH; + action = SELECT_BOX; + rt_kprintf("select touch switch\n"); + } + else if (x >= g_area_array[5].start_x && x <= g_area_array[5].end_x && y >= g_area_array[5].start_y && y <= g_area_array[5].end_y) + { + settings_selected_idx = SET_TIMEOUT; + action = SELECT_BOX; + rt_kprintf("select timeout switch\n"); + } + else if (x >= g_area_array[8].start_x && x <= g_area_array[8].end_x && y >= g_area_array[8].start_y && y <= g_area_array[8].end_y) + { + settings_selected_idx = SET_FULL_REFRESH; + action = SELECT_BOX; + rt_kprintf("select full refresh switch \n"); + } + else if (x >= g_area_array[9].start_x && x <= g_area_array[9].end_x && y >= g_area_array[9].start_y && y <= g_area_array[9].end_y) + { + settings_selected_idx = SET_CONFIRM; + action = SELECT; + rt_kprintf("select confirm button\n"); + } + + + if(settings_selected_idx == SET_TOUCH && g_area_array[0].start_x<=x && x<= g_area_array[0].end_x && g_area_array[0].start_y<=y && y<=g_area_array[0].end_y) + { + action = SELECT; + } + else if(settings_selected_idx == SET_TOUCH && g_area_array[1].start_x<=x && x<= g_area_array[1].end_x && g_area_array[1].start_y<=y && y<=g_area_array[1].end_y) + { + action = SELECT; + } + else if(settings_selected_idx == SET_TIMEOUT && g_area_array[3].start_x<=x && x<= g_area_array[3].end_x && g_area_array[3].start_y<=y && y<=g_area_array[3].end_y) + { + action = PREV_OPTION; + rt_kprintf("select timeout Reduce\n"); + } + else if(settings_selected_idx == SET_TIMEOUT && g_area_array[4].start_x<=x && x<= g_area_array[4].end_x && g_area_array[4].start_y<=y && y<=g_area_array[4].end_y) + { + action = NEXT_OPTION; + rt_kprintf("select timeout increase\n"); + } + else if(settings_selected_idx == SET_FULL_REFRESH && g_area_array[6].start_x<=x && x<= g_area_array[6].end_x && g_area_array[6].start_y<=y && y<=g_area_array[6].end_y) + { + action = PREV_OPTION; + } + else if(settings_selected_idx == SET_FULL_REFRESH && g_area_array[7].start_x<=x && x<= g_area_array[7].end_x && g_area_array[7].start_y<=y && y<=g_area_array[7].end_y) + { + action = NEXT_OPTION; + } + break; + +} + + - } instance->last_action = action; if (action != NONE) { @@ -76,21 +444,6 @@ SF32_TouchControls::SF32_TouchControls(Renderer *renderer, ActionCallback_t on_a void SF32_TouchControls::render(Renderer *renderer) { - renderer->set_margin_top(0); - uint16_t x_offset = 10; - uint16_t x_triangle = x_offset + 70; - // DOWN - renderer->draw_rect(x_offset, 1, ui_button_width, ui_button_height, 0); - renderer->draw_triangle(x_triangle, 20, x_triangle - 5, 6, x_triangle + 5, 6, 0); - // UP - x_offset = ui_button_width + 30; - x_triangle = x_offset + 70; - renderer->draw_rect(x_offset, 1, ui_button_width, ui_button_height, 0); - renderer->draw_triangle(x_triangle, 6, x_triangle - 5, 20, x_triangle + 5, 20, 0); - // SELECT - x_offset = ui_button_width * 2 + 60; - renderer->draw_rect(x_offset, 1, ui_button_width, ui_button_height, 0); - renderer->draw_circle(x_offset + (ui_button_width / 2) + 9, 15, 5, 0); renderer->set_margin_top(35); } @@ -104,7 +457,6 @@ void SF32_TouchControls::powerOffTouch() rt_kprintf("no touch device found\n"); } } - void SF32_TouchControls::powerOnTouch() { if (tp_device) { @@ -116,47 +468,5 @@ void SF32_TouchControls::powerOnTouch() } void SF32_TouchControls::renderPressedState(Renderer *renderer, UIAction action, bool state) { - renderer->set_margin_top(0); - switch (action) - { - case DOWN: - { - if (state) - { - renderer->fill_triangle(80, 20, 75, 6, 85, 6, 0); - } - else - { - renderer->fill_triangle(81, 19, 76, 7, 86, 7, 255); - } - //renderer->flush_area(76, 6, 10, 15); - break; - } - case UP: - { - if (state) - { - renderer->fill_triangle(220, 6, 220 - 5, 20, 220 + 5, 20, 0); - } - else - { - renderer->fill_triangle(221, 7, 221 - 5, 19, 221 + 5, 19, 255); - } - //renderer->flush_area(195, 225, 10, 15); - } - break; - case SELECT: - { - uint16_t x_circle = (ui_button_width * 2 + 60) + (ui_button_width / 2) + 9; - renderer->fill_circle(x_circle, 15, 5, 0); - //renderer->flush_area(x_circle - 3, 12, 6, 6); - // TODO - this causes a stack overflow when select is picked - // renderPressedState(renderer, last_action, false); - } - break; - case LAST_INTERACTION: - case NONE: - break; - } renderer->set_margin_top(35); } \ No newline at end of file diff --git a/epdiy-epub/src/boards/controls/SF32_TouchControls.h b/epdiy-epub/src/boards/controls/SF32_TouchControls.h index 23f2c56..0ad4b12 100644 --- a/epdiy-epub/src/boards/controls/SF32_TouchControls.h +++ b/epdiy-epub/src/boards/controls/SF32_TouchControls.h @@ -14,7 +14,11 @@ class SF32_TouchControls : public TouchControls uint8_t ui_button_width = 120; uint8_t ui_button_height = 34; UIAction last_action = NONE; - + // 添加手势检测状态 + bool is_touch_down = false; // 是否正在触摸 + int touch_start_y = 0; // 按下时的 Y 坐标 + int touch_current_y = 0; // 当前触摸 Y 坐标 + // 滑动检测阈值 public: static rt_err_t tp_rx_indicate(rt_device_t dev, rt_size_t size); @@ -23,4 +27,10 @@ class SF32_TouchControls : public TouchControls void renderPressedState(Renderer *renderer, UIAction action, bool state = true) override; void powerOnTouch() override; void powerOffTouch() override; -}; \ No newline at end of file +}; + +// 最近一次设置页左右箭头触控标记 +// 行号:0=触控开关,1=超时关机,2=全刷周期;-1=无 +extern volatile int g_touch_last_settings_row; +// 方向:-1=左(减),+1=右(加),0=无 +extern volatile int g_touch_last_settings_dir; \ No newline at end of file diff --git a/epdiy-epub/src/boards/controls/TouchControls.h b/epdiy-epub/src/boards/controls/TouchControls.h index b0c8ba9..cc04d1f 100644 --- a/epdiy-epub/src/boards/controls/TouchControls.h +++ b/epdiy-epub/src/boards/controls/TouchControls.h @@ -8,7 +8,7 @@ class Renderer; class TouchControls { protected: - bool touch_enable = false; + bool touch_enable = 1; public: TouchControls(){}; diff --git a/epdiy-epub/src/boards/display_dbi/epd_configs_opm060d.c b/epdiy-epub/src/boards/display_dbi/epd_configs_opm060d.c index 0d75d1d..608b473 100644 --- a/epdiy-epub/src/boards/display_dbi/epd_configs_opm060d.c +++ b/epdiy-epub/src/boards/display_dbi/epd_configs_opm060d.c @@ -176,4 +176,4 @@ const EPD_TimingConfig *epd_get_timing_config(void) return &timing_config; } -#endif /*LCD_USING_OPM060D*/ \ No newline at end of file +#endif /*LCD_USING_OPM060D*/ diff --git a/epdiy-epub/src/boards/display_dbi/epd_display.c b/epdiy-epub/src/boards/display_dbi/epd_display.c index 2bd6d80..badd965 100644 --- a/epdiy-epub/src/boards/display_dbi/epd_display.c +++ b/epdiy-epub/src/boards/display_dbi/epd_display.c @@ -91,6 +91,9 @@ static uint32_t wait_lcd_ticks; static uint16_t epic_out_buffer_idx = 0; static uint16_t epic_out_buffer[2][LCD_HOR_RES_MAX]; static uint32_t lut_copy_ticks; + +static int g_part_disp_times = 10; // After g_part_disp_times-1 partial refreshes, perform a full refresh once +static int reflesh_times = 0; // Total number of refreshes performed /* Define a mixed grey framebuffer on PSRAM high 4 bits for old pixel and low 4 bits for new pixel in every byte. @@ -525,10 +528,17 @@ void epd_load_and_send_pic(LCDC_HandleTypeDef *hlcdc, uint32_t line_type, const } } +void set_part_disp_times(int val) +{ + g_part_disp_times = val > 0 ? val : 1; + reflesh_times = 1; +} +int get_part_disp_times(void) +{ + return g_part_disp_times; +} #define PART_DISP_TIMES 10 -static uint32_t reflesh_times = 0; - L1_RET_CODE_SECT(epd_codes, static void LCD_WriteMultiplePixels(LCDC_HandleTypeDef *hlcdc, const uint8_t *RGBCode, uint16_t Xpos0, uint16_t Ypos0, uint16_t Xpos1, uint16_t Ypos1)) { uint32_t line, line_bytes; @@ -557,16 +567,22 @@ L1_RET_CODE_SECT(epd_codes, static void LCD_WriteMultiplePixels(LCDC_HandleTypeD uint8_t temperature = 26; - - if (reflesh_times % PART_DISP_TIMES == 0) { - frame_times = epd_wave_table_get_frames(temperature, EPD_DRAW_MODE_FULL); - reflesh_times = 0; - } else { - frame_times = epd_wave_table_get_frames(temperature, EPD_DRAW_MODE_PARTIAL); + EpdDrawMode mode; + if (reflesh_times % g_part_disp_times == 0) + { + rt_kprintf("cleared all \n"); + mode = EPD_DRAW_MODE_FULL; + } + else + { + rt_kprintf("executing partial refresh, this is the %dth partial refresh (there are %d partial refreshes left until the next full refresh)\n", + (reflesh_times % g_part_disp_times), + g_part_disp_times - (reflesh_times % g_part_disp_times)); + mode = EPD_DRAW_MODE_PARTIAL; } - reflesh_times++; - + frame_times = epd_wave_table_get_frames(temperature, mode); + CopyToMixedGrayBuffer(hlcdc, RGBCode, Xpos0, Ypos0, Xpos1, Ypos1); LOG_I("Convert layer data take=%d(ms) \r\n", rt_tick_get() - start_tick); @@ -673,6 +689,8 @@ L1_RET_CODE_SECT(epd_codes, static void LCD_WriteMultiplePixels(LCDC_HandleTypeD rt_tick_get() - start_tick, wait_lcd_ticks / 240, lut_copy_ticks / 240); + reflesh_times++; + EPD_GMODE_L_hs(); EPD_STV_L_hs(); TPS_WAKEUP_L_hs(); diff --git a/epdiy-epub/src/boards/display_spi/epd_display.c b/epdiy-epub/src/boards/display_spi/epd_display.c index 60860bd..09f0563 100644 --- a/epdiy-epub/src/boards/display_spi/epd_display.c +++ b/epdiy-epub/src/boards/display_spi/epd_display.c @@ -47,10 +47,11 @@ #define REG_VDCS 0x82 #define REG_WRITE_NEW_DATA 0x13 -static int reflesh_times; +static int reflesh_times = 0; static uint8_t current_refresh_mode; static unsigned char LUT_Flag = 0; // LUT切换标志 static unsigned char Var_Temp = 0; // 温度值 +static int g_part_disp_times = 10; // After g_part_disp_times-1 partial refreshes, perform a full refresh once static LCDC_InitTypeDef lcdc_int_cfg = { .lcd_itf = LCDC_INTF_SPI_DCX_1DATA, @@ -79,6 +80,16 @@ static void EPD_LoadLUT(LCDC_HandleTypeDef *hlcdc, uint8_t lut_mode); static rt_sem_t epd_busy_sem = RT_NULL; +void set_part_disp_times(int val) +{ + g_part_disp_times = val > 0 ? val : 1; + reflesh_times = 1; +} +int get_part_disp_times(void) +{ + return g_part_disp_times; +} + static void epd_busy_callback(void *args) { rt_sem_release(epd_busy_sem); @@ -123,18 +134,19 @@ static void EPD_ReadBusy(void) } static uint8_t epd_get_refresh_mode(void) { - uint8_t mode = 2; // 默认局刷(DU模式) - - if (reflesh_times % PART_DISP_TIMES == 0) + uint8_t mode; + if (reflesh_times % g_part_disp_times == 0) { - mode = 1; // 全刷(GC模式) - } - - if (Var_Temp < 0 || Var_Temp > 50) + rt_kprintf("cleared all \n"); + mode = 1; //全刷 + } + else { - mode = 1; + rt_kprintf("executing partial refresh, this is the %dth partial refresh (there are %d partial refreshes left until the next full refresh)\n", + (reflesh_times % g_part_disp_times), + g_part_disp_times - (reflesh_times % g_part_disp_times)); + mode = 2; //局刷 } - current_refresh_mode = mode; return mode; } diff --git a/epdiy-epub/src/epub_mem.c b/epdiy-epub/src/epub_mem.c index b15b636..c875865 100644 --- a/epdiy-epub/src/epub_mem.c +++ b/epdiy-epub/src/epub_mem.c @@ -80,4 +80,4 @@ rt_uint32_t heap_free_size(void) rt_uint32_t psram_heap_free_size = epub_psram_memheap.available_size; return heap_free_size + psram_heap_free_size; -} +} \ No newline at end of file diff --git a/epdiy-epub/src/epub_screen.cpp b/epdiy-epub/src/epub_screen.cpp new file mode 100644 index 0000000..cf9d359 --- /dev/null +++ b/epdiy-epub/src/epub_screen.cpp @@ -0,0 +1,559 @@ + +#include "EpubList/EpubList.h" +#include "epub_screen.h" +#include +#include "type.h" + +#include "UIRegionsManager.h" + + +extern TouchControls *touch_controls; +extern "C" +{ + extern void set_part_disp_times(int val); +} + +// 最近一次真实打开并阅读的书本索引(由 main.cpp 维护) +extern int g_last_read_index; + +// 主页面选项 +typedef enum +{ + OPTION_OPEN_LIBRARY = 0, // 打开书库 + OPTION_CONTINUE_READING, // 继续阅读 + OPTION_ENTER_SETTINGS // 进入设置 +} MainOption; + +static MainOption main_option = OPTION_OPEN_LIBRARY; // 默认“打开书库” +// 全刷周期选项:5、10、20、每次(0) +static const int kFullRefreshOptions[] = {5, 10, 20, 0}; +static const int kFullRefreshOptionsCount = sizeof(kFullRefreshOptions) / sizeof(kFullRefreshOptions[0]); +static int full_refresh_idx = 1; // 默认10次 + +// 获取当前全刷周期值 +int screen_get_full_refresh_period() +{ + return kFullRefreshOptions[full_refresh_idx]; +} + +// 切换全刷周期(循环) +void screen_cycle_full_refresh_period(bool refresh) +{ + if(refresh) + { + full_refresh_idx = (full_refresh_idx + 1) % kFullRefreshOptionsCount; // ?% 4 + + } + else + { + full_refresh_idx = (full_refresh_idx - 1) % kFullRefreshOptionsCount; // ?% 4 + + } +} + +// 设置全刷周期索引 +void screen_set_full_refresh_idx(int idx) +{ + if (idx >= 0 && idx < kFullRefreshOptionsCount) full_refresh_idx = idx; +} + +// 获取当前全刷周期索引 +int screen_get_full_refresh_idx() +{ + return full_refresh_idx; +} + + +int settings_selected_idx = 0; + +// 超时关机:5/10/30分钟、1小时、不关机(0) +static const int kTimeoutOptions[] = {5, 10, 30, 60, 0}; // 单位:分钟,0为不关机 +static const int kTimeoutOptionsCount = sizeof(kTimeoutOptions) / sizeof(kTimeoutOptions[0]); +static int timeout_shutdown_minutes = 30; // 默认30分钟 +static int timeout_idx = -1; // + +static int find_timeout_idx(int minutes) +{ + for (int i = 0; i < kTimeoutOptionsCount; ++i) + { + if (kTimeoutOptions[i] == minutes) return i; + } + return 2; // 默认索引:30分钟 +} + +static void adjust_timeout(bool increase) +{ + if (timeout_idx < 0) timeout_idx = find_timeout_idx(timeout_shutdown_minutes); + if (increase) + { + timeout_idx = (timeout_idx + 1) % kTimeoutOptionsCount; + } + else + { + timeout_idx = (timeout_idx - 1) % kTimeoutOptionsCount; + } + timeout_shutdown_minutes = kTimeoutOptions[timeout_idx]; +} + +void screen_init(int default_timeout_minutes) +{ + timeout_shutdown_minutes = default_timeout_minutes; + timeout_idx = find_timeout_idx(timeout_shutdown_minutes); +} + +int screen_get_timeout_shutdown_minutes() +{ + if (timeout_idx < 0) timeout_idx = find_timeout_idx(timeout_shutdown_minutes); + return timeout_shutdown_minutes; +} + +int screen_get_main_selected_option() +{ + return (int)main_option; // 0: 打开书库, 1: 继续阅读, 2: 进入设置 +} + +// 绘制主页面 +static void render_main_page(Renderer *renderer) +{ + + clear_areas(); // 清除之前的区域记录 + + renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 255); + + const char *title = "S I F L I"; + int title_w = renderer->get_text_width(title); + int title_h = renderer->get_line_height(); + int center_x = renderer->get_page_width() / 2; + int center_y = 35 + (renderer->get_page_height() - 35) / 2; + renderer->draw_text(center_x - title_w / 2, center_y - title_h / 2, title, true, true); + + int margin_side = 10; + int margin_bottom = 60; // 与底部距离 + int rect_w = 80; + int rect_h = 40; + int y = renderer->get_page_height() - rect_h - margin_bottom; + int left_x = margin_side; + int right_x = renderer->get_page_width() - rect_w - margin_side; + + // 左 "<" + const char *lt = "<"; + int lt_w = renderer->get_text_width(lt); + int lt_h = renderer->get_line_height(); + + int left_arrow_x = margin_side;//矩形的X轴起始坐标 + int left_arrow_y = y + margin_bottom;//矩形的Y轴起始坐标 + // 记录左箭头区域 + add_area(left_arrow_x, left_arrow_y, rect_w, rect_h); + + + renderer->draw_text(left_x + (rect_w - lt_w) / 2, y + (rect_h - lt_h) / 2, lt, false, true); + + // 右 ">" + const char *gt = ">"; + int gt_w = renderer->get_text_width(gt); + int gt_h = renderer->get_line_height(); + + int right_arrow_x = right_x ;//矩形的X轴起始坐标 + int right_arrow_y = y + margin_bottom;//矩形的Y轴起始坐标 + // 记录右箭头区域 + add_area(right_arrow_x, right_arrow_y, rect_w, rect_h); + + + renderer->draw_text(right_x + (rect_w - gt_w) / 2, y + (rect_h - gt_h) / 2, gt, false, true); + + // 中间选项文本 + int mid_x = left_x + rect_w + margin_side; + int mid_w = right_x - margin_side - mid_x; + + const char *opt_text = NULL; + extern EpubListState epub_list_state; + bool has_continue_reading = (epub_list_state.num_epubs > 0 && g_last_read_index >= 0 && g_last_read_index < epub_list_state.num_epubs); + switch (main_option) + { + case OPTION_OPEN_LIBRARY: opt_text = "打开书库"; break; + case OPTION_CONTINUE_READING: + opt_text = has_continue_reading ? "继续阅读" : "无阅读记录"; + break; + case OPTION_ENTER_SETTINGS: opt_text = "进入设置"; break; + } + int opt_w = renderer->get_text_width(opt_text); + int opt_h = renderer->get_line_height(); + + int option_x = mid_x + (mid_w - opt_w) / 2 ; + int option_y = y + margin_bottom; + + // 记录选项区域 + add_area(option_x, option_y, opt_w, opt_h); + + renderer->draw_text(mid_x + (mid_w - opt_w) / 2, y + (rect_h - opt_h) / 2, opt_text, false, true); +} +//主界面处理 +void handleMainPage(Renderer *renderer, UIAction action, bool needs_redraw) +{ + if (needs_redraw || action == NONE) + { + render_main_page(renderer);//绘制主界面 + return; + } + switch (action) + { + case UP: // 左切换 + if (main_option == OPTION_OPEN_LIBRARY) main_option = OPTION_ENTER_SETTINGS; + else if (main_option == OPTION_CONTINUE_READING) main_option = OPTION_OPEN_LIBRARY; + else main_option = OPTION_CONTINUE_READING; + render_main_page(renderer); + break; + case DOWN: // 右切换 + if (main_option == OPTION_OPEN_LIBRARY) main_option = OPTION_CONTINUE_READING; + else if (main_option == OPTION_CONTINUE_READING) main_option = OPTION_ENTER_SETTINGS; + else main_option = OPTION_OPEN_LIBRARY; + render_main_page(renderer); + break; + case SELECT: + // 由上层 main.cpp 负责切换 页面UIState + switch (main_option) + { + case OPTION_OPEN_LIBRARY: + rt_kprintf("1\n"); + break; + case OPTION_CONTINUE_READING: + rt_kprintf("2\n"); + break; + case OPTION_ENTER_SETTINGS: + rt_kprintf("3\n"); + break; + } + break; + default: + break; + } +} + +// 设置页面 +void render_settings_page(Renderer *renderer) +{ + + clear_areas(); // 清除之前的区域记录 + + renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 255); + + // 标题 + const char *title = "设置"; + int title_w = renderer->get_text_width(title); + int title_h = renderer->get_line_height(); + int page_w = renderer->get_page_width(); + int page_h = renderer->get_page_height(); + renderer->draw_text((page_w - title_w) / 2, 40, title, true, true); + + // 列表项布局参数 + int margin_lr = 6; // 左右边距,给左右触控箭头 + int item_h = 100; // 矩形高度 + int gap = 54; // 列表项之间的间距 + int arrow_col_w = 40; // 左右触控箭头列宽度 + int y = 40 + title_h + 20; // 第一项起始Y + + // 1) 触控开关 + int item_w = page_w - margin_lr * 2 - arrow_col_w * 2; // 为左右箭头列留边 + int item_x = margin_lr + arrow_col_w; + if (settings_selected_idx == SET_TOUCH) + { + + const char *lt = "<"; + int lt_w = renderer->get_text_width(lt); + int touch_left_x = margin_lr; + int touch_left_y = y; + static_add_area(touch_left_x, touch_left_y, arrow_col_w, item_h,0); + renderer->draw_text(margin_lr + (arrow_col_w - lt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, lt, false, true); + + const char *gt = ">"; int gt_w = renderer->get_text_width(gt); + int touch_right_x = page_w - arrow_col_w + margin_lr; + int touch_right_y = y; + static_add_area(touch_right_x, touch_right_y, arrow_col_w, item_h,1); + + renderer->draw_text(page_w - margin_lr - arrow_col_w + (arrow_col_w - gt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, gt, false, true); + } + if (settings_selected_idx == SET_TOUCH) + { + // 选中强化:多重描边,提高可见度 + for (int i = 0; i < 5; ++i) renderer->draw_rect(item_x + i, y + i, item_w - 2 * i, item_h - 2 * i, 0); + } + else + { + renderer->draw_rect(item_x, y, item_w, item_h, 0); //画框线 + } + bool touch_on = touch_controls ? touch_controls->isTouchEnabled() : false; + char buf1[48]; + rt_snprintf(buf1, sizeof(buf1), "触控开关:%s", touch_on ? "开" : "关"); + int t1_w = renderer->get_text_width(buf1); + int lh = renderer->get_line_height(); + { + int tx = item_x + (item_w - t1_w) / 2; + if (tx < item_x + 4) tx = item_x + 4; + if (tx + t1_w > item_x + item_w - 4) tx = item_x + item_w - t1_w - 4; + + int touch_switch_x = item_x; + int touch_switch_y = y ; + static_add_area(touch_switch_x, touch_switch_y, item_w, item_h,2); + + renderer->draw_text(tx, y + (item_h - lh) / 2, buf1, false, true); + } + y += item_h + gap; + + // 2) 超时关机 + if (settings_selected_idx == SET_TIMEOUT) + { + + const char *lt = "<"; + int lt_w = renderer->get_text_width(lt); + int timeout_left_x = margin_lr; + int timeout_left_y = y; + static_add_area(timeout_left_x, timeout_left_y, arrow_col_w, item_h,3); + renderer->draw_text(margin_lr + (arrow_col_w - lt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, lt, false, true); + + const char *gt = ">"; + int gt_w = renderer->get_text_width(gt); + int timeout_right_x = page_w - arrow_col_w + margin_lr; + int timeout_right_y = y; + static_add_area(timeout_right_x, timeout_right_y, arrow_col_w, item_h,4); + + renderer->draw_text(page_w - margin_lr - arrow_col_w + (arrow_col_w - gt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, gt, false, true); + } + if (settings_selected_idx == SET_TIMEOUT) + { + for (int i = 0; i < 5; ++i) renderer->draw_rect(item_x + i, y + i, item_w - 2 * i, item_h - 2 * i, 0); + } + else + { + renderer->draw_rect(item_x, y, item_w, item_h, 0); + } + char buf2[64]; + if (timeout_shutdown_minutes == 0) + { + rt_snprintf(buf2, sizeof(buf2), "超时关机:不关机"); + } + else if (timeout_shutdown_minutes < 60) + { + rt_snprintf(buf2, sizeof(buf2), "超时关机:%d分钟", timeout_shutdown_minutes); + } + else + { + rt_snprintf(buf2, sizeof(buf2), "超时关机:%d小时", timeout_shutdown_minutes / 60); + } + { + int t2_w = renderer->get_text_width(buf2); + int tx = item_x + (item_w - t2_w) / 2; + if (tx < item_x + 4) tx = item_x + 4; + if (tx + t2_w > item_x + item_w - 4) tx = item_x + item_w - t2_w - 4; + + int timeout_setting_x = item_x; + int timeout_setting_y = y; + static_add_area(timeout_setting_x, timeout_setting_y, item_w, item_h,5); + + renderer->draw_text(tx, y + (item_h - lh) / 2, buf2, false, true); + } + y += item_h + gap; + + // 3) 全刷周期 + if (settings_selected_idx == SET_FULL_REFRESH) + { + + const char *lt = "<"; + int lt_w = renderer->get_text_width(lt); + int full_refresh_left_x = margin_lr; + int full_refresh_left_y = y; + static_add_area(full_refresh_left_x, full_refresh_left_y, arrow_col_w, item_h,6); + renderer->draw_text(margin_lr + (arrow_col_w - lt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, lt, false, true); + + const char *gt = ">"; + int gt_w = renderer->get_text_width(gt); + int full_refresh_right_x = page_w - arrow_col_w + margin_lr; + int full_refresh_right_y = y; + static_add_area(full_refresh_right_x, full_refresh_right_y, arrow_col_w, item_h,7); + + renderer->draw_text(page_w - margin_lr - arrow_col_w + (arrow_col_w - gt_w) / 2, y + (item_h - renderer->get_line_height()) / 2, gt, false, true); + } + if (settings_selected_idx == SET_FULL_REFRESH) + { + for (int i = 0; i < 5; ++i) renderer->draw_rect(item_x + i, y + i, item_w - 2 * i, item_h - 2 * i, 0); + } + else + { + renderer->draw_rect(item_x, y, item_w, item_h, 0); + } + char buf3[64]; + int fr_val = screen_get_full_refresh_period(); + if (fr_val == 0) + rt_snprintf(buf3, sizeof(buf3), "全刷周期:每次"); + else + rt_snprintf(buf3, sizeof(buf3), "全刷周期:%d 次", fr_val); + { + int t3_w = renderer->get_text_width(buf3); + int tx = item_x + (item_w - t3_w) / 2; + if (tx < item_x + 4) tx = item_x + 4; + if (tx + t3_w > item_x + item_w - 4) tx = item_x + item_w - t3_w - 4; + + int full_refresh_setting_x = item_x; + int full_refresh_setting_y = y; + static_add_area(full_refresh_setting_x, full_refresh_setting_y, item_w, item_h,8); + + renderer->draw_text(tx, y + (item_h - lh) / 2, buf3, false, true); + } + y += item_h + gap; + + // 底部 确认 按钮 + int confirm_h = 120; // 矩形框高度 + int confirm_w = item_w; // 宽度 + int confirm_x = (page_w - confirm_w) / 2; // 居中 + int confirm_y = page_h - confirm_h - 60; // 距离底部位置 + if (settings_selected_idx == SET_CONFIRM) + { + for (int i = 0; i < 5; ++i) renderer->draw_rect(confirm_x + i, confirm_y + i, confirm_w - 2 * i, confirm_h - 2 * i, 0); + } + else + { + renderer->draw_rect(confirm_x, confirm_y, confirm_w, confirm_h, 0); + } + const char *confirm = "确认"; + int c_w = renderer->get_text_width(confirm); + int c_h = renderer->get_line_height(); + + int confirm_button_x = confirm_x; + int confirm_button_y = confirm_y; + static_add_area(confirm_button_x, confirm_button_y, confirm_w, confirm_h,9); + + renderer->draw_text(confirm_x + (confirm_w - c_w) / 2, confirm_y + (confirm_h - c_h) / 2, confirm, false, true); +} + +// 设置页面交互处理 +bool handleSettingsPage(Renderer *renderer, UIAction action, bool needs_redraw) +{ + // 读取并清除一次性的触控箭头标记,避免后续硬件按键误用 + int touch_row = g_touch_last_settings_row; + int touch_dir = g_touch_last_settings_dir; + g_touch_last_settings_row = -1; + g_touch_last_settings_dir = 0; + + if (needs_redraw || action == NONE) + { + render_settings_page(renderer); + return false; + } + + switch (action) + { + case UP: + // 触控箭头若命中“超时关机”行且为左箭头(减),执行减;否则执行上下选择 + if (settings_selected_idx == SET_TIMEOUT && touch_row == 1 && touch_dir == -1) + { + adjust_timeout(false); + render_settings_page(renderer); + } + else + { + if (settings_selected_idx > 0) settings_selected_idx--; else settings_selected_idx = SET_CONFIRM; + render_settings_page(renderer); + } + break; + case DOWN: + // 触控箭头若命中“超时关机”行且为右箭头(加),执行加;否则执行上下选择 + if (settings_selected_idx == SET_TIMEOUT && touch_row == 1 && touch_dir == +1) + { + adjust_timeout(true); + render_settings_page(renderer); + } + else + { + if (settings_selected_idx < SET_CONFIRM) settings_selected_idx++; else settings_selected_idx = SET_TOUCH; + render_settings_page(renderer); + } + break; + case SELECT_BOX: + if(settings_selected_idx == SET_TOUCH) + { + render_settings_page(renderer); + } + else if(settings_selected_idx == SET_TIMEOUT) + { + render_settings_page(renderer); + } + else if(settings_selected_idx == SET_FULL_REFRESH) + { + render_settings_page(renderer); + } + else if(settings_selected_idx == SET_CONFIRM) + { + render_settings_page(renderer); + return true; + } + break; + case PREV_OPTION: + if (settings_selected_idx == SET_TIMEOUT) + { + // SELECT 在超时关机项上为加操作(循环) + adjust_timeout(false); + render_settings_page(renderer); + } + else if(settings_selected_idx == SET_FULL_REFRESH) + { + + screen_cycle_full_refresh_period(false); + set_part_disp_times(screen_get_full_refresh_period()); + render_settings_page(renderer); + } + + break; + case NEXT_OPTION: + if (settings_selected_idx == SET_TIMEOUT) + { + // SELECT 在超时关机项上为加操作(循环) + adjust_timeout(true); + render_settings_page(renderer); + } + else if(settings_selected_idx == SET_FULL_REFRESH) + { + + screen_cycle_full_refresh_period(true); + set_part_disp_times(screen_get_full_refresh_period()); + render_settings_page(renderer); + } + break; + case SELECT: + if (settings_selected_idx == SET_TOUCH) + { + bool current_state = touch_controls ? touch_controls->isTouchEnabled() : false; + if (touch_controls) + { + touch_controls->setTouchEnable(!current_state); + if (!current_state) touch_controls->powerOnTouch(); + else touch_controls->powerOffTouch(); + } + render_settings_page(renderer); + break; + } + if (settings_selected_idx == SET_TIMEOUT) + { + // SELECT 在超时关机项上为加操作(循环) + adjust_timeout(true); + render_settings_page(renderer); + break; + } + if (settings_selected_idx == SET_FULL_REFRESH) + { + // SELECT 在全刷周期项上为加操作(循环) + screen_cycle_full_refresh_period(true); + set_part_disp_times(screen_get_full_refresh_period()); + render_settings_page(renderer); + break; + } + if (settings_selected_idx == SET_CONFIRM) + { + // 由上层切回主页面 + return true; + } + // 其他项当前不处理 + break; + default: + break; + } + return false; +} \ No newline at end of file diff --git a/epdiy-epub/src/epub_screen.h b/epdiy-epub/src/epub_screen.h new file mode 100644 index 0000000..148141b --- /dev/null +++ b/epdiy-epub/src/epub_screen.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "boards/SF32PaperRenderer.h" +#include "boards/controls/Actions.h" +#include "boards/controls/TouchControls.h" +#include "boards/controls/SF32_TouchControls.h" + + +// 初始化屏幕模块(设置默认的关机超时小时数,0 表示不关机) +void screen_init(int default_timeout_hours); + +// 获取当前关机超时设置(小时;0 表示不关机) +int screen_get_timeout_shutdown_minutes(); + +// 获取当前主页面选中的选项(0: 打开书库, 1: 继续阅读, 2: 进入设置) +int screen_get_main_selected_option(); + +// 主页面交互与渲染 +void handleMainPage(Renderer *renderer, UIAction action, bool needs_redraw); + +// 设置页面交互与渲染;返回 true 表示确认并退出到主页面 +bool handleSettingsPage(Renderer *renderer, UIAction action, bool needs_redraw); +// 切换全刷周期(循环) +void screen_cycle_full_refresh_period(bool refresh); +// 获取当前全刷周期值 +int screen_get_full_refresh_period(); \ No newline at end of file diff --git a/epdiy-epub/src/main.cpp b/epdiy-epub/src/main.cpp index 171f083..3f7957c 100644 --- a/epdiy-epub/src/main.cpp +++ b/epdiy-epub/src/main.cpp @@ -5,17 +5,22 @@ #include "EpubList/EpubToc.h" #include #include "boards/Board.h" +#include "boards/controls/Actions.h" #include "boards/controls/SF32_TouchControls.h" +#include "epub_screen.h" #include "boards/SF32PaperRenderer.h" #include "gui_app_pm.h" #include "bf0_pm.h" #include "epd_driver.h" +#include "type.h" + +#include "UIRegionsManager.h" #undef LOG_TAG #undef DBG_LEVEL #define DBG_LEVEL DBG_LOG //DBG_INFO // #define LOG_TAG "EPUB.main" - +#define TIMEOUT_SHUTDOWN_TIME 5 // 默认关机超时(小时);0 表示不关机 #include @@ -24,89 +29,242 @@ extern "C" { int main(); rt_uint32_t heap_free_size(void); + extern void set_part_disp_times(int val); extern const uint8_t low_power_map[]; extern const uint8_t chargeing_map[]; extern const uint8_t welcome_map[]; extern const uint8_t shutdown_map[]; } - const char *TAG = "main"; -typedef enum -{ - SELECTING_EPUB, - SELECTING_TABLE_CONTENTS, - READING_EPUB, -} UIState; -typedef enum -{ - MAIN_MENU, - WELCOME_PAGE, - LOW_POWER_PAGE, - CHARGING_PAGE -} UIState2; - -// default to showing the list of epubs to the user -UIState ui_state = SELECTING_EPUB; -UIState2 lowpower_ui_state = MAIN_MENU; + + +// 默认显示新主页面,而非书库页面 +AppUIState ui_state = MAIN_PAGE; // the state data for the epub list and reader EpubListState epub_list_state; // the state data for the epub index list EpubTocState epub_index_state; +// 最近一次真实打开并阅读的书本索引(-1 表示无记录) +int g_last_read_index = -1; + void handleEpub(Renderer *renderer, UIAction action); void handleEpubList(Renderer *renderer, UIAction action, bool needs_redraw); +void back_to_main_page(); static EpubList *epub_list = nullptr; -static EpubReader *reader = nullptr; +EpubReader *reader = nullptr; static EpubToc *contents = nullptr; static bool charge_full = false; Battery *battery = nullptr; -// 声明全局变量,以便open_tp_lcd和close_tp_lcd函数可以访问 +// 给open_tp_lcd和close_tp_lcd用的 Renderer *renderer = nullptr; TouchControls *touch_controls = nullptr; +// 书库页底部按钮选择状态 +bool library_bottom_mode = false; // 是否处于底部三按钮选择模式 +int library_bottom_idx = 1; // 当前底部按钮索引:0上一页,1主页面,2下一页 +int book_index;//用于记录电子书触控选择 +int current_page; // 当前页面 +int start_index; // 当前页起始索引 +// 计算全局索引 = 页起始索引 + 页内偏移 +int global_index; +bool toc_bottom_mode = false; +int toc_index;//用于记录目录触控选择 +int toc_bottom_idx = 1; // 0:上一页,1:主页面,2:下一页 +int sel; +int touch_sel; rt_mq_t ui_queue = RT_NULL; +// 主页面选项 +typedef enum { + OPTION_OPEN_LIBRARY = 0, // 打开书库 -> 打印 1 + OPTION_CONTINUE_READING, // 继续阅读 -> 打印 2 + OPTION_ENTER_SETTINGS // 进入设置 -> 打印 3 +} MainOption; +void handleEpubTableContents(Renderer *renderer, UIAction action, bool needs_redraw); + + +//阅读设置页面 + void handleEpub(Renderer *renderer, UIAction action) { - if (!reader) - { - reader = new EpubReader(epub_list_state.epub_list[epub_list_state.selected_item], renderer); - reader->load(); - } - switch (action) - { - case UP: - reader->prev(); - break; - case DOWN: - reader->next(); - break; - case SELECT: - - // switch back to main screen - ui_state = SELECTING_EPUB; - renderer->clear_screen(); - // clear the epub reader away - delete reader; - reader = nullptr; - // force a redraw - if (!epub_list) + if (!reader) { - epub_list = new EpubList(renderer, epub_list_state); + reader = new EpubReader(epub_list_state.epub_list[epub_list_state.selected_item], renderer); + reader->load(); + // 记录最近一次进入阅读的书籍索引 + g_last_read_index = epub_list_state.selected_item; } - handleEpubList(renderer, NONE, true); + + switch (action) + { + case UP: + if (reader->is_overlay_active()) + { + reader->overlay_move_left(); + } + else + { + reader->prev(); + } + break; + case DOWN: + if (reader->is_overlay_active()) + { + reader->overlay_move_right(); + } + else + { + reader->next(); + } + break; + case SELECT: + if (reader->is_overlay_active()) + { + int sel = reader->get_overlay_selected(); + // 1/3:改变中心属性;2:执行当前属性(触控取反 / 全刷周期循环) + if(touch_sel >=0 && touch_sel <=10) + { + sel = -1; + } + if (sel == 0 || touch_sel == 0) + { + if(reader->overlay_is_center_touch()) + { + reader->overlay_set_center_mode_full_refresh(); + } + else + { + reader->overlay_set_center_mode_touch(); + } + } + else if (sel == 2 || touch_sel == 2) + { + if(reader->overlay_is_center_touch()) + { + reader->overlay_set_center_mode_full_refresh(); + } + else + { + reader->overlay_set_center_mode_touch(); + } + } + else if (sel == 1 || touch_sel == 1) + { + // 中心矩形:根据当前属性执行 + if (reader->overlay_is_center_touch()) + { + bool cur = touch_controls ? touch_controls->isTouchEnabled() : false; + if (touch_controls) + { + touch_controls->setTouchEnable(!cur); + if (!cur) touch_controls->powerOnTouch(); else touch_controls->powerOffTouch(); + } + reader->overlay_set_touch_enabled(!cur); + } + else + { + reader->overlay_cycle_full_refresh(); //设置全刷周期,在 5/10/20/每次(0) 之间循环 + set_part_disp_times(reader->overlay_get_full_refresh_value()); + } + } + if (sel == 9 || touch_sel == 9) //目录 + { + ui_state = SELECTING_TABLE_CONTENTS; + renderer->set_margin_bottom(0); + reader->stop_overlay(); + delete reader; + reader = nullptr; + contents = new EpubToc(epub_list_state.epub_list[epub_list_state.selected_item], epub_index_state, renderer); + contents->load(); + contents->set_needs_redraw(); + handleEpubTableContents(renderer, NONE, true); + touch_sel = -1; + return; + } + else if (sel == 8 || touch_sel == 8) //确认:1.按第六格累积值跳页 + { + // 跳转到第六格显示的目标页 + int target = reader->overlay_get_target_page(); + if (target < 1) target = 1; + extern EpubListState epub_list_state; + epub_list_state.epub_list[epub_list_state.selected_item].current_page = (uint16_t)(target - 1); + reader->overlay_reset_jump(); + reader->stop_overlay(); + } + else if (sel == 10 || touch_sel == 10) //书库 + { + ui_state = SELECTING_EPUB; + renderer->set_margin_bottom(0); + reader->stop_overlay(); + renderer->clear_screen(); + delete reader; + reader = nullptr; + if (!epub_list) + { + epub_list = new EpubList(renderer, epub_list_state); + } + handleEpubList(renderer, NONE, true); + touch_sel = -1; + return; + } + else if (sel == 3 || touch_sel == 3) + { + reader->overlay_adjust_target_page(-5); + } + else if (sel == 4 || touch_sel == 4) + { + reader->overlay_adjust_target_page(-1); + } + else if (sel == 6 || touch_sel == 6) + { + reader->overlay_adjust_target_page(1); + } + else if (sel == 7 || touch_sel == 7) + { + reader->overlay_adjust_target_page(5); + } + touch_sel = -1; + } + else + { + // switch back to main screen + renderer->clear_screen(); + // clear the epub reader away + delete reader; + reader = nullptr; + // force a redraw + if (!epub_list) + { + epub_list = new EpubList(renderer, epub_list_state); + } + renderer->set_margin_bottom(0); + back_to_main_page(); - return; - case NONE: - default: - break; - } - reader->render(); + return; + } + break; + case UPGLIDE: + // 激活阅读页下半屏覆盖操作层 + // 防止重复激活 + if (!reader->is_overlay_active()) { + reader->start_overlay(); + // 默认中心属性为触控开关,初始同步当前触控状态 + reader->overlay_set_center_mode_touch(); + if (touch_controls) + reader->overlay_set_touch_enabled(touch_controls->isTouchEnabled()); + } + break; + case NONE: + default: + break; + } + reader->render(); } - +//目录页的处理 void handleEpubTableContents(Renderer *renderer, UIAction action, bool needs_redraw) { if (!contents) @@ -115,32 +273,204 @@ void handleEpubTableContents(Renderer *renderer, UIAction action, bool needs_red contents->set_needs_redraw(); contents->load(); } + + if (needs_redraw) + { + toc_bottom_mode = false; + toc_bottom_idx = 1; + } switch (action) { case UP: - contents->prev(); + if (toc_bottom_mode) + { + // 底部模式下:UP 向左移动;若已在最左(上一页),则返回当前页目录的最后一项 + if (toc_bottom_idx > 0) + { + toc_bottom_idx--; + } + else + { + int per_page = 6; + int start_idx = (epub_index_state.selected_item / per_page) * per_page; + int end_idx = start_idx + per_page - 1; + int count = contents->get_items_count(); + if (end_idx >= count) end_idx = count - 1; + toc_bottom_mode = false; + epub_index_state.selected_item = end_idx; + } + } + else + { + // 若处于当前页第一个条目,UP 切换到底部按钮模式并选择“下一页” + int per_page = 6; + int start_idx = (epub_index_state.selected_item / per_page) * per_page; + if (contents->get_items_count() > 0 && epub_index_state.selected_item == start_idx) + { + toc_bottom_mode = true; + toc_bottom_idx = 2; // 下一页 + } + else + { + contents->prev(); + } + } break; case DOWN: - contents->next(); + if (toc_bottom_mode) + { + // 底部模式下:DOWN 向右移动;若已在最右(下一页),则返回当前页目录的第一项 + if (toc_bottom_idx < 2) + { + toc_bottom_idx++; + } + else + { + int per_page = 6; + int start_idx = (epub_index_state.selected_item / per_page) * per_page; + toc_bottom_mode = false; + epub_index_state.selected_item = start_idx; + } + } + else + { + int per_page = 6; + int start_idx = (epub_index_state.selected_item / per_page) * per_page; + int end_idx = start_idx + per_page - 1; + int count = contents->get_items_count(); + if (end_idx >= count) end_idx = count - 1; + // 若处于当前页最后一个条目,DOWN 切换到底部按钮模式并选择“上一页” + if (count > 0 && epub_index_state.selected_item == end_idx) + { + toc_bottom_mode = true; + toc_bottom_idx = 0; // 上一页 + } + else + { + contents->next(); + } + } + break; + case SELECT_BOX: + // 如果在底部模式,先退出底部模式 + if (toc_bottom_mode) + { + toc_bottom_mode = false; + } + // 计算当前页面相关信息 + current_page = epub_index_state.selected_item / 6; // 每页6个目录项 + start_index = current_page * 6; // 当前页起始索引 + // 计算全局索引 = 页起始索引 + 页内偏移 + global_index = start_index + toc_index; + // 边界检查:确保点击的目录项存在 + if (global_index < contents->get_items_count() && contents->get_items_count() > 0) + { + // 更新选中的目录项 + epub_index_state.selected_item = global_index; + + // 根据toc_index确定点击的是哪个位置的目录项(0-5) + switch(toc_index) + { + case 0: + contents->switch_book(global_index); + break; + case 1: + contents->switch_book(global_index); + break; + case 2: + contents->switch_book(global_index); + break; + case 3: + contents->switch_book(global_index); + break; + case 4: + contents->switch_book(global_index); + break; + case 5: + contents->switch_book(global_index); + break; + default: + break; + } + } break; case SELECT: - // setup the reader state - ui_state = READING_EPUB; - // create the reader and load the book - reader = new EpubReader(epub_list_state.epub_list[epub_list_state.selected_item], renderer); - reader->set_state_section(contents->get_selected_toc()); - reader->load(); - //switch to reading the epub - delete contents; - handleEpub(renderer, NONE); - return; + if (toc_bottom_mode) + { + int per_page = 6; + int count = contents->get_items_count(); + int current_page = (count > 0) ? (epub_index_state.selected_item / per_page) : 0; + int max_page = (count == 0) ? 0 : ((count - 1) / per_page); + if (toc_bottom_idx == 1) + { + // 书库:切换到书库页面 + rt_kprintf("从目录页返回书库页\n"); + ui_state = SELECTING_EPUB; + if (contents) + { + delete contents; + contents = nullptr; + } + handleEpubList(renderer, NONE, true); + return; + } + else if (toc_bottom_idx == 0) // 上一页 + { + if (current_page > 0) + { + // 计算新页面(上一页) + int new_page = current_page - 1; + // 将选中项设置为新页面的第一项 + epub_index_state.selected_item = new_page * per_page; + if (epub_index_state.selected_item < 0) + epub_index_state.selected_item = 0; + + // 退出底部选择模式,回到列表选择状态 + toc_bottom_mode = false; + contents->set_needs_redraw(); + } + } + else if (toc_bottom_idx == 2) // 下一页 + { + if (current_page < max_page) + { + // 计算新页面(下一页) + int new_page = current_page + 1; + // 将选中项设置为新页面的第一项 + epub_index_state.selected_item = new_page * per_page; + if (epub_index_state.selected_item >= count) + epub_index_state.selected_item = ((count - 1) / per_page) * per_page; // 确保在最后一页的第一项 + + // 退出底部选择模式,回到列表选择状态 + toc_bottom_mode = false; + contents->set_needs_redraw(); + } + } + } + else + { + // 进入阅读界面 + ui_state = READING_EPUB; + reader = new EpubReader(epub_list_state.epub_list[epub_list_state.selected_item], renderer); + reader->set_state_section(contents->get_selected_toc()); + reader->load(); + // 记录最近一次进入阅读的书籍索引 + g_last_read_index = epub_list_state.selected_item; + delete contents; + handleEpub(renderer, NONE); + return; + } + break; case NONE: default: break; } + // 将底部选择状态传递给目录渲染 + contents->set_bottom_selection(toc_bottom_mode, toc_bottom_idx); contents->render(); } +//书库页的处理 void handleEpubList(Renderer *renderer, UIAction action, bool needs_redraw) { // load up the epub list from the filesystem @@ -157,56 +487,178 @@ void handleEpubList(Renderer *renderer, UIAction action, bool needs_redraw) if (needs_redraw) { epub_list->set_needs_redraw(); + // 进入书库页时重置底部选择状态 + library_bottom_mode = false; + library_bottom_idx = 1; } // work out what the user wants us to do switch (action) { case UP: - epub_list->prev(); + if (library_bottom_mode) + { + // 底部模式下:UP 向左移动;若已在最左(上一页),则返回当前页的列表最后一项 + if (library_bottom_idx > 0) + { + library_bottom_idx--; + } + else + { + int per_page = 4; + int start_idx = (epub_list_state.selected_item / per_page) * per_page; + int end_idx = start_idx + per_page - 1; + if (end_idx >= epub_list_state.num_epubs) end_idx = epub_list_state.num_epubs - 1; + library_bottom_mode = false; + epub_list_state.selected_item = end_idx; + } + } + else + { + // 若处于当前页第一个条目,UP 切换到底部按钮模式 + int per_page = 4; + int start_idx = (epub_list_state.selected_item / per_page) * per_page; + if (epub_list_state.num_epubs > 0 && epub_list_state.selected_item == start_idx) + { + library_bottom_mode = true; + library_bottom_idx = 2; // 下一页 + } + else + { + epub_list->prev(); + } + } break; case DOWN: - epub_list->next(); - break; - case SELECT: - // 检查是否选中了底部特殊区域 - if (epub_list_state.selected_item == -1) { - // 打印"1" - rt_kprintf("touch open or off\n"); - bool current_state = touch_controls->isTouchEnabled(); - touch_controls->setTouchEnable(!current_state); - - // 刷新屏幕以更新底部区域的文本显示 - if (!current_state) // 之前是关闭状态,现在要打开 - { - touch_controls->powerOnTouch(); + if (library_bottom_mode) + { + // 底部模式下:DOWN 向右移动;若已在最右(下一页),则返回当前页的列表第一项 + if (library_bottom_idx < 2) + { + library_bottom_idx++; } - else // 之前是打开状态,现在要关闭 + else { - touch_controls->powerOffTouch(); + int per_page = 4; + int start_idx = (epub_list_state.selected_item / per_page) * per_page; + int end_idx = start_idx + per_page - 1; + if (end_idx >= epub_list_state.num_epubs) end_idx = epub_list_state.num_epubs - 1; + library_bottom_mode = false; + epub_list_state.selected_item = start_idx; } + } + else + { + int per_page = 4; + int start_idx = (epub_list_state.selected_item / per_page) * per_page; + int end_idx = start_idx + per_page - 1; + if (end_idx >= epub_list_state.num_epubs) end_idx = epub_list_state.num_epubs - 1; + // 若处于当前页最后一个条目,DOWN 切换到底部按钮模式 + if (epub_list_state.num_epubs > 0 && epub_list_state.selected_item == end_idx) + { + library_bottom_mode = true; + library_bottom_idx = 0; // 上一页 + } + else + { + epub_list->next(); + } + } + break; + case SELECT_BOX: + // 如果在底部模式,先退出底部模式 + if (library_bottom_mode) { + library_bottom_mode = false; + } + current_page = epub_list_state.selected_item / 4; // 当前页面 + start_index = current_page * 4; // 当前页起始索引 + // 计算全局索引 = 页起始索引 + 页内偏移 + global_index = start_index + book_index; + // 边界检查 + if (global_index < epub_list_state.num_epubs) + { - epub_list->render(); - - - return; - } - else + if(book_index == 0) + { + epub_list->switch_book(global_index); + } + else if(book_index == 1) + { + epub_list->switch_book(global_index); + } + else if(book_index == 2) + { + epub_list->switch_book(global_index); + } + else if(book_index == 3) + { + epub_list->switch_book(global_index); + } + } + break; + case SELECT: + if (library_bottom_mode) { - // switch to reading the epub - // setup the reader state - ui_state = SELECTING_TABLE_CONTENTS; - // create the reader and load the book - contents = new EpubToc(epub_list_state.epub_list[epub_list_state.selected_item], epub_index_state, renderer); - contents->load(); - contents->set_needs_redraw(); - handleEpubTableContents(renderer, NONE, true); - return; + int per_page = 4; + int current_page = epub_list_state.selected_item / per_page; + int max_page = (epub_list_state.num_epubs == 0) ? 0 : ( (epub_list_state.num_epubs - 1) / per_page ); + if (library_bottom_idx == 1) + { + // 主页面:返回主页面 + rt_kprintf("从书库页返回主页面\n"); + back_to_main_page(); + return; + } + else if (library_bottom_idx == 0) // 上一页 + { + if (current_page > 0) + { + // 计算新页面(上一页) + int new_page = current_page - 1; + // 将选中项设置为新页面的第一本书 + epub_list_state.selected_item = new_page * per_page; + if (epub_list_state.selected_item < 0) + epub_list_state.selected_item = 0; + + // 退出底部选择模式,回到列表选择状态 + library_bottom_mode = false; + epub_list->set_needs_redraw(); + } + } + else if (library_bottom_idx == 2) // 下一页 + { + if (current_page < max_page) + { + // 计算新页面(下一页) + int new_page = current_page + 1; + // 将选中项设置为新页面的第一本书 + epub_list_state.selected_item = new_page * per_page; + if (epub_list_state.selected_item >= epub_list_state.num_epubs) + epub_list_state.selected_item = ((epub_list_state.num_epubs - 1) / per_page) * per_page; // 确保在最后一页的第一本书 + + // 退出底部选择模式,回到列表选择状态 + library_bottom_mode = false; + epub_list->set_needs_redraw(); + } + } } + else + { + // 进入目录选择页面 + ui_state = SELECTING_TABLE_CONTENTS; + contents = new EpubToc(epub_list_state.epub_list[epub_list_state.selected_item], epub_index_state, renderer); + contents->load(); + contents->set_needs_redraw(); + handleEpubTableContents(renderer, NONE, true); + return; + } + break; case NONE: default: // nothing to do break; } + // 将底部选择状态传递给列表渲染 + epub_list->set_bottom_selection(library_bottom_mode, library_bottom_idx); epub_list->render(); } // TODO - add the battery level @@ -288,13 +740,52 @@ void handleUserInteraction(Renderer *renderer, UIAction ui_action, bool needs_re uint32_t start_tick = rt_tick_get(); switch (ui_state) { + case MAIN_PAGE: // 新主页面 + handleMainPage(renderer, ui_action, needs_redraw); + if (ui_action == SELECT && screen_get_main_selected_option() == 2) //切换到设置页面 + { + ui_state = SETTINGS_PAGE; + (void)handleSettingsPage(renderer, NONE, true); + } + else if (ui_action == SELECT && screen_get_main_selected_option() == 1) //继续阅读 + { + // 判断是否有继续阅读记录 + if (!(g_last_read_index >= 0 && g_last_read_index < epub_list_state.num_epubs)) { + return; // 无记录,忽略 + } + // 有记录,恢复阅读 + if (reader) { delete reader; reader = nullptr; } + int last_idx = g_last_read_index; + EpubListItem &last_item = epub_list_state.epub_list[last_idx]; + reader = new EpubReader(last_item, renderer); + reader->set_state_section(last_item.current_section); + reader->load(); + ui_state = READING_EPUB; + handleEpub(renderer, NONE); + } + else if (ui_action == SELECT && screen_get_main_selected_option() == 0) //切换到书库页面 + { + ui_state = SELECTING_EPUB; + handleEpubList(renderer, NONE, true); + } + break; case READING_EPUB: //阅读界面 handleEpub(renderer, ui_action); break; case SELECTING_TABLE_CONTENTS: //目录界面 handleEpubTableContents(renderer, ui_action, needs_redraw); break; - case SELECTING_EPUB: //电子书列表(主界面) + case SETTINGS_PAGE: // 设置页面 + { + bool exit_to_main = handleSettingsPage(renderer, ui_action, needs_redraw); + if (exit_to_main) + { + ui_state = MAIN_PAGE; + handleMainPage(renderer, NONE, true); + } + break; + } + case SELECTING_EPUB: //电子书列表页面(书库页面) default: handleEpubList(renderer, ui_action, needs_redraw); break; @@ -302,157 +793,154 @@ void handleUserInteraction(Renderer *renderer, UIAction ui_action, bool needs_re rt_kprintf("Renderer time=%d \r\n", rt_tick_get() - start_tick); } const char* getCurrentPageName() { - switch (lowpower_ui_state) - { - case MAIN_MENU: - return "MAIN_MENU"; - case WELCOME_PAGE: - return "WELCOME_PAGE"; - case LOW_POWER_PAGE: - return "LOW_POWER_PAGE"; - case CHARGING_PAGE: - return "CHARGING_PAGE"; - default: - return "UNKNOWN_PAGE"; - } + switch (ui_state) + { + case MAIN_PAGE: + return "MAIN_PAGE"; + case SELECTING_EPUB: + return "SELECTING_EPUB"; + case SELECTING_TABLE_CONTENTS: + return "SELECTING_TABLE_CONTENTS"; + case READING_EPUB: + return "READING_EPUB"; + case SETTINGS_PAGE: + return "SETTINGS_PAGE"; + case WELCOME_PAGE: + return "WELCOME_PAGE"; + case LOW_POWER_PAGE: + return "LOW_POWER_PAGE"; + case CHARGING_PAGE: + return "CHARGING_PAGE"; + case SHUTDOWN_PAGE: + return "SHUTDOWN_PAGE"; + default: + return "UNKNOWN_PAGE"; + } } //回到主界面接口 void back_to_main_page() -{ - if (strcmp(getCurrentPageName(), "MAIN_MENU") == 0) - { - return; - } - lowpower_ui_state = MAIN_MENU; - if (ui_state == SELECTING_TABLE_CONTENTS) - { - if (contents) - { - delete contents; - contents = nullptr; - } +{ + if (ui_state == MAIN_PAGE) + { + rt_kprintf("已经在主页面,无需返回\n"); + return; + } + if (ui_state == SELECTING_TABLE_CONTENTS) + { + if (contents) + { + delete contents; + contents = nullptr; } - bool hydrate_success = renderer->hydrate(); - - renderer->reset(); - renderer->set_margin_top(35); - renderer->set_margin_left(10); - renderer->set_margin_right(10); - - if (!epub_list) - { - epub_list = new EpubList(renderer, epub_list_state); - if (epub_list->load("/")) - { - ulog_i("main", "Epub files loaded"); - } - } - handleUserInteraction(renderer, NONE, true); - - if (battery) - { - draw_charge_status(renderer, battery); - draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); - } - touch_controls->render(renderer); - renderer->flush_display(); + } + bool hydrate_success = renderer->hydrate(); + + renderer->reset(); + renderer->set_margin_top(35); + renderer->set_margin_left(10); + renderer->set_margin_right(10); + // 返回新的主页面,不再默认进入书库页面 + ui_state = MAIN_PAGE; + handleUserInteraction(renderer, NONE, true); + if (battery) + { + draw_charge_status(renderer, battery); + draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); + } + touch_controls->render(renderer); + renderer->flush_display(); } //欢迎页面 void draw_welcome_page(Battery *battery) { - if (strcmp(getCurrentPageName(), "WELCOME_PAGE") == 0) - { - return; - } - lowpower_ui_state = WELCOME_PAGE; - touch_controls->powerOffTouch(); - touch_controls->setTouchEnable(false); - // 设置黑色背景 - renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); - if (battery) { - renderer->set_margin_top(35); - draw_charge_status(renderer, battery); - draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); - } + if (ui_state == WELCOME_PAGE) + { + return; + } + ui_state = WELCOME_PAGE; + // 设置黑色背景 + renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); + if (battery) { + renderer->set_margin_top(35); + draw_charge_status(renderer, battery); + draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); + } - const int img_width = 649; - const int img_height = 150; - - int center_x = renderer->get_page_width() / 2; - int center_y = 35 + (renderer->get_page_height() - 35) / 2; - int x_pos = center_x - img_width / 2; - int y_pos = center_y - img_height / 2; - - EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); - fb_renderer->show_img(x_pos, y_pos, img_width, img_height, welcome_map); + const int img_width = 649; + const int img_height = 150; - // 显示 - renderer->flush_display(); - + int center_x = renderer->get_page_width() / 2; + int center_y = 35 + (renderer->get_page_height() - 35) / 2; + int x_pos = center_x - img_width / 2; + int y_pos = center_y - img_height / 2; + + EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); + fb_renderer->show_img(x_pos, y_pos, img_width, img_height, welcome_map); + + // 显示 + renderer->flush_display(); } -// 绘制低电量页面 +// 低电量页面 void draw_low_power_page(Battery *battery) { - if (strcmp(getCurrentPageName(), "LOW_POWER_PAGE") == 0) - { - return; - } - lowpower_ui_state = LOW_POWER_PAGE; - - // 设置黑色背景 - renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); - if (battery) { - renderer->set_margin_top(35); - draw_charge_status(renderer, battery); - draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); - } + if (ui_state == LOW_POWER_PAGE) + { + return; + } + ui_state = LOW_POWER_PAGE; + // 设置黑色背景 + renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); + if (battery) { + renderer->set_margin_top(35); + draw_charge_status(renderer, battery); + draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); + } - const int img_width = 200; - const int img_height = 200; - - int center_x = renderer->get_page_width() / 2; - int center_y = 35 + (renderer->get_page_height() - 35) / 2; - int x_pos = center_x - img_width / 2; - int y_pos = center_y - img_height / 2; - - EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); - fb_renderer->show_img(x_pos, y_pos, img_width, img_height, low_power_map); - // 显示 - renderer->flush_display(); - + const int img_width = 200; + const int img_height = 200; + + int center_x = renderer->get_page_width() / 2; + int center_y = 35 + (renderer->get_page_height() - 35) / 2; + int x_pos = center_x - img_width / 2; + int y_pos = center_y - img_height / 2; + + EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); + fb_renderer->show_img(x_pos, y_pos, img_width, img_height, low_power_map); + // 显示 + renderer->flush_display(); } //充电页面 void draw_charge_page(Battery *battery) { - if (strcmp(getCurrentPageName(), "CHARGING_PAGE") == 0) - { - return; - } - lowpower_ui_state = CHARGING_PAGE; - // 设置黑色背景 - renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); - if (battery) { - renderer->set_margin_top(35); - draw_charge_status(renderer, battery); - draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); - } + if (ui_state == CHARGING_PAGE) + { + return; + } + ui_state = CHARGING_PAGE; + // 设置黑色背景 + renderer->fill_rect(0, 0, renderer->get_page_width(), renderer->get_page_height(), 0); + if (battery) { + renderer->set_margin_top(35); + draw_charge_status(renderer, battery); + draw_battery_level(renderer, battery->get_voltage(), battery->get_percentage()); + } - const int img_width = 200; - const int img_height = 200; - - int center_x = renderer->get_page_width() / 2; - int center_y = 35 + (renderer->get_page_height() - 35) / 2; - int x_pos = center_x - img_width / 2; - int y_pos = center_y - img_height / 2; - - EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); - fb_renderer->show_img(x_pos, y_pos, img_width, img_height, chargeing_map); - // 显示 - renderer->flush_display(); + const int img_width = 200; + const int img_height = 200; + + int center_x = renderer->get_page_width() / 2; + int center_y = 35 + (renderer->get_page_height() - 35) / 2; + int x_pos = center_x - img_width / 2; + int y_pos = center_y - img_height / 2; + + EpdiyFrameBufferRenderer* fb_renderer = static_cast(renderer); + fb_renderer->show_img(x_pos, y_pos, img_width, img_height, chargeing_map); + // 显示 + renderer->flush_display(); } //关机页面 @@ -538,6 +1026,7 @@ void main_task(void *param) // reset the screen renderer->reset(); // make sure the UI is in the right state + ui_state = MAIN_PAGE; handleUserInteraction(renderer, NONE, true); } @@ -557,18 +1046,22 @@ void main_task(void *param) // keep track of when the user last interacted and go to sleep after N seconds rt_tick_t last_user_interaction = rt_tick_get_millisecond(); + // 初始化屏幕模块默认关机超时 + screen_init(TIMEOUT_SHUTDOWN_TIME); -while (rt_tick_get_millisecond() - last_user_interaction < 60 * 1000 * 60 *5) //5小时无操作自动关机 -{ + while ((rt_tick_get_millisecond() - last_user_interaction < 60 * 1000 * 60 * TIMEOUT_SHUTDOWN_TIME)) // 5小时 + { - // 检查是否超过5分钟无操作,如果是在欢迎页面、充电页面或低电量页面则不跳转 - if (rt_tick_get_millisecond() - last_user_interaction >= 60 * 1000 *5 && - battery && battery->get_low_power_state() != 1 && - strcmp(getCurrentPageName(), "WELCOME_PAGE") != 0 && - strcmp(getCurrentPageName(), "CHARGING_PAGE") != 0 && - strcmp(getCurrentPageName(), "LOW_POWER_PAGE") != 0) + // 检查是否超过设置分钟无操作,如果是在欢迎页面、充电页面或低电量页面则不跳转 + if (rt_tick_get_millisecond() - last_user_interaction >= 60 * 1000 * screen_get_timeout_shutdown_minutes() && + battery && battery->get_low_power_state() != 1 && + ui_state != WELCOME_PAGE && + ui_state != CHARGING_PAGE && + ui_state != LOW_POWER_PAGE && + screen_get_timeout_shutdown_minutes()) { - draw_welcome_page(battery); + renderer->set_margin_bottom(0); + draw_welcome_page(battery); } uint32_t msg_data; if (rt_mq_recv(ui_queue, &msg_data, sizeof(uint32_t), rt_tick_from_millisecond(60500)) == RT_EOK) //一分钟自动刷一下 @@ -608,6 +1101,7 @@ while (rt_tick_get_millisecond() - last_user_interaction < 60 * 1000 * 60 *5) // { case MSG_DRAW_LOW_POWER_PAGE: rt_kprintf("low_power\n"); + renderer->set_margin_bottom(0); draw_low_power_page(battery); break; case MSG_DRAW_CHARGE_PAGE: @@ -630,14 +1124,13 @@ while (rt_tick_get_millisecond() - last_user_interaction < 60 * 1000 * 60 *5) // if (ui_action != NONE) { // 如果之前在欢迎页面,现在需要返回主界面 - if(strcmp(getCurrentPageName(), "WELCOME_PAGE") == 0) + if(ui_state == WELCOME_PAGE) { back_to_main_page(); - last_user_interaction = rt_tick_get_millisecond(); board->sleep_filesystem(); continue; - } + } //rt_kprintf("ui_action = %d\n", ui_action); // something happened! last_user_interaction = rt_tick_get_millisecond(); @@ -671,6 +1164,7 @@ while (rt_tick_get_millisecond() - last_user_interaction < 60 * 1000 * 60 *5) // // turn off the filesystem board->stop_filesystem(); // get ready to go to sleep + renderer->set_margin_bottom(0); draw_shutdown_page(); board->prepare_to_sleep(); //ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup()); @@ -687,7 +1181,7 @@ extern "C" int main() { // dump out the epub list state - //rt_pm_request(PM_SLEEP_MODE_IDLE); + rt_pm_request(PM_SLEEP_MODE_IDLE); ulog_i("main", "epub list state num_epubs=%d", epub_list_state.num_epubs); ulog_i("main", "epub list state is_loaded=%d", epub_list_state.is_loaded); ulog_i("main", "epub list state selected_item=%d", epub_list_state.selected_item); diff --git a/epdiy-epub/src/type.h b/epdiy-epub/src/type.h new file mode 100644 index 0000000..820485d --- /dev/null +++ b/epdiy-epub/src/type.h @@ -0,0 +1,38 @@ +#ifndef TYPES_H +#define TYPES_H + +// typedef enum { +// MAIN_PAGE, +// SELECTING_EPUB, +// SELECTING_TABLE_CONTENTS, +// READING_EPUB, +// SETTINGS_PAGE +// } UIState; + +// typedef enum { +// MAIN_MENU, +// WELCOME_PAGE, +// LOW_POWER_PAGE, +// CHARGING_PAGE +// } UIState2; + +// 设置页列表项 +typedef enum { + SET_TOUCH = 0, + SET_TIMEOUT = 1, + SET_FULL_REFRESH = 2, + SET_CONFIRM = 3 +} SettingsItem; + +typedef enum { + MAIN_PAGE, // 新主页面 + SELECTING_EPUB, // 电子书列表页面(书库) + SELECTING_TABLE_CONTENTS, // 电子书目录页面 + READING_EPUB, // 阅读页面 + SETTINGS_PAGE, // 通用功能设置页面 + WELCOME_PAGE, // 欢迎页面 + LOW_POWER_PAGE, // 低电量页面 + CHARGING_PAGE, // 充电页面 + SHUTDOWN_PAGE // 关机页面 +} AppUIState; +#endif \ No newline at end of file