52e28885cb
三个薄页面模块 ui_page_clock / ui_page_reset / ui_page_clear,页面注册由生成器 自动写双端(裁定 A-1:「三叶各绑一页」与「零新增 .c」不可兼得,选薄页面加生成器, 不写手写 node switch——那种分发在增删叶子时会静默换功能,且没有任何门禁照得到)。 数据侧是注入式函数表:UI 不 include App 层的头。两条硬理由都实测过——分层方向 (layer-deps)之外,模拟器根本不链 sys_clock,ALLOW 豁免挡得住 layer-deps, 挡不住模拟器链接期。面板对时的唯一真判据是回读 RTC:sys_clock_set() 恒返 0 (RTC 写失败只打日志),所以真机侧写 1 后回读 1,最坏约 200 ms 落在一次「保存」 按键上,IWDG 800 ms 窗有余;上界与依据都写进注释。 「无时钟」徽标位置有两个像素级实测坑:放标题带右侧会被 4 字标题连成「时钟设置 无时钟」一句话;放页脚带左侧 x=7 会压住页脚——页脚从 x=6 起排,不是居中。 最终落在清单末行与页脚之间的空带 y=124。clear_done 那张顺带给出像素证据: 清 run.log 之后「操作记录」那一行从 2 变 3,页面上没缓存、没撒谎。 ui_w_records 的 UI_W_REC_LOG_* 顺序必须等于 syslog_id_t 顺序(7 类,只准尾追加), 已加编译期顺序守卫;执行侧 7 条映射表同样带编译期守卫。 测试与真值:内容页黄金 27 → 40 页(+13),文本断言 +68、反向断言 ABSENT +19; 27 张旧参考 git diff 为空 = 逐字节未变。往返池 5448 B 与菜单常驻堆 5368 B 均未变; 字库未扩(源码在用 171 并 KEEP_EXTRA 39 = 清单 183),font-charset 与 cn-chars、 cn-font 字节一致。@build 0 错误 0 警告;uiTask 静态最坏链 1,336 B / 16,384 (余 91%)与基线相同,页面钩子没进最坏链。 一条测试学结论单独留痕:反向断言不能当「位置不重叠」的证据——本脚本的字形匹配 把 y 带判得比字面宽(徽标在 125..136、页脚在 144..155,仍被 (140,160) 判命中)。 该防线改由正向断言「页脚未被徽标压住」承担(压字那一版实测过不了它)。 两条自我更正一并记档(作者自报,按真值而非原口径): ① 先前报「文案零新增汉字」是错的——观测行拟用「动作元件」,「元」不在子集, cn-chars 与 font-charset 如实红;最终改「保护动作 N」才让「零新增」成为事实。 顺序是:先按机器红,再按真值写结论。 ② 先前报「6 个文件 BOM 逐个核过」被 bom-guard 证伪:丢 BOM 发生在上一次编译之后, 正是 AGENTS 那句「历史 0 警告不代表安全」的活例;此后一律以退出码为准。 未覆盖项不假装:台架「N>0 → N=0」在产品构建的未标定闭锁期拿不到(闭锁期 N 恒 0), 判据显式退化为「页面受理 + 落一条操作记录」,写在 rs_watch 注释里。 生成物入库例外照用:uvprojx 只加 3 个 ui_page_ 的 FilePath(真增删源文件), CMakeLists 同批;两处都由 gen_menu.py 自己写,没手写。 本笔先走了一次真钩子:20 条钩子实跑通过(ui-golden 菜单 8 状态加内容页 40 页、 generated-fresh、registration-check、font-charset、ui-data 单测、cppcheck、 coding-standard、bom、mojibake、id-check、doc-links 等),只有 mem-numbers 与 mem-budget 两条红——磁盘上的 .map 出自 20:39 那次构建、含三条在飞道的代码, 与 HEAD 台账必然不同码。按仓库既有约定(这两条等台账回填时允许 --no-verify 并 正文留证)落这一笔;回填由我统一在收尾一笔做。 三条在飞道(task-26 崩溃证据路径、task-16 与 task-17 谓词收敛和卷锁观测、 task-36 台架工具)的未暂存改动被 autostash 暂时收走,钩子看到的是 HEAD 加本笔; 过程中曾把 comm 道未跟踪的 iec104_comm.c 临时移出(它不在 stash 覆盖面内), 那次移出反而让 ui-golden 撞见 CMake 缓存里的悬空引用而红——已改用「先落 comm 那笔、再落本笔」的顺序消掉这个中间态。ui_data_maint_ready() 待删(只有测试在读 的自检口),排在本笔之后单独一笔。
389 lines
17 KiB
C
389 lines
17 KiB
C
/*
|
||
* 模块名称:UI Page clock (时钟设置内容页)
|
||
* 模块功能:装置维护 → 时钟设置 叶子(node 24,#P 受密码保护)的内容页——
|
||
* 六字段表单(年/月/日/时/分/秒)+ 保存行(仅可编辑态才有),
|
||
* 预填取自 ui_data 实时帧的 clock[20] + clock_ok(本文件不 include
|
||
* 任何 App 头,尤其**不碰 sys_clock**:模拟器没链 sys_clock,页面里
|
||
* 用一次就是"MCU 编得过、模拟器链接期爆"),字段编辑复用 ui_w_editor
|
||
* (逐位 ±1、左右移光标、ENTER 提交、越界拒退并提示),
|
||
* 保存走 ui_data_maint_clock_set() 注入转发——执行侧真机实现
|
||
* = sys_clock_set_unix() + 回读复核,返 0 才是终态;
|
||
* 无效值拒收:六字段先过 ui_data_clock_to_epoch() 整组校验
|
||
* (含 2 月 30 日、非闰年 2 月 29 日这类"每段都合法、组合非法"),
|
||
* 不过则**一个字节都不写**、保存行显 ERR;
|
||
* 墙钟无效(clock_ok=0 或串解析失败):六行值列一律 "--" + 标题右侧
|
||
* "无时钟"徽标,**绝不显示也绝不预填 1970**(口径见下方 CK_INVAL 注)
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:DTU 项目组
|
||
* 创建日期:2026-10-26
|
||
* 修改记录:
|
||
* v1.0 2026-10-26 DTU 项目组 创建(道F task-3;判据 docs/任务规划/UI补齐.md
|
||
* §2 第 3 行「时钟设置写入 sys_clock_set_unix()
|
||
* 并回读」;形态 = 页面扩充详细方案.md §3.4 表单 6 字段)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_clock.h"
|
||
|
||
#include "ui_data.h"
|
||
#include "ui_tokens.h"
|
||
#include "ui_w_editor.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
#define CK_FIELD_N 6u /* 字段行数:年月日时分秒 */
|
||
#define CK_ROW_COUNT_MAX 7u /* 最多行数:6 字段 + 1 保存行 */
|
||
#define CK_SAVE_ROW CK_FIELD_N /* 保存行下标(仅可编辑态存在) */
|
||
#define CK_BADGE_X 7 /* 徽标左边界:与行名同一列 */
|
||
#define CK_BADGE_Y 124 /* 徽标上边界:清单最后一行(108..121)与页脚带(140+)
|
||
之间的空带,实测三条链都没占过它 */
|
||
#define CK_NO_CLOCK_TXT "无时钟" /* 徽标(用字已在字库) */
|
||
#define CK_INVAL_VAL "--" /* 无效墙钟的字段值列——**不是 1970**:
|
||
记录侧那条 "1970-01-01 08:00:00" 是
|
||
sys_log 写死占位串(App/log/sys_log_mcu.c
|
||
落盘时 sys_clock_get_str 返 NULL 才用),
|
||
把它抄来预填等于让用户"照抄保存",
|
||
一次点击就把 1970 写进 RTC —— 比不显示更坏 */
|
||
|
||
/* 保存行值列词表:与整定保存行、网口设置保存行同一排 ASCII 词。
|
||
* 没有 WAIT 这一档:维护通道是**同步转发**,返回值就是执行侧带回读结论的终态,
|
||
* 屏上挂一个永远等不到的 WAIT 比不挂更让人困惑(契约 §6.3 第 2 条同一口径) */
|
||
#define CK_ST_ENTER "ENTER"
|
||
#define CK_ST_OK "OK"
|
||
#define CK_ST_ERR "ERR"
|
||
|
||
/*
|
||
* 本地这条操作记录的描述串:ASCII 18 字节 < SYSLOG_DESC_MAX(28)。
|
||
* 与远端 "clock set by 104"(App/task/iec104_serv.c)对称——面板与主站各一条、
|
||
* 串不同,查"这装置什么时候被谁对过时"才分得开。只在保存成功那条腿投。
|
||
*/
|
||
#define CK_OPER_NOTE "clock set by panel"
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* 字段区间:日的"当月真实上限"不在这里判(编辑器只有静态 min/max),
|
||
* 组合合法性统一由 ui_data_clock_to_epoch() 在保存时把关——少一处第二判据 */
|
||
static const uint16_t k_ck_min[CK_FIELD_N] = {
|
||
(uint16_t)UI_DATA_CLOCK_YEAR_MIN, 1u, 1u, 0u, 0u, 0u
|
||
};
|
||
static const uint16_t k_ck_max[CK_FIELD_N] = {
|
||
(uint16_t)UI_DATA_CLOCK_YEAR_MAX, 12u, 31u, 23u, 59u, 59u
|
||
};
|
||
static const char *const k_ck_name[CK_FIELD_N] = {
|
||
"年", "月", "日", "时", "分", "秒"
|
||
};
|
||
|
||
/* 实时帧缓存:只在发布序号变过时整帧读(§5.4 行缓存纪律;本层不做逐行整帧拷贝) */
|
||
static ui_data_rt_t s_rt;
|
||
static uint32_t s_rt_seq;
|
||
|
||
static uint16_t s_val[CK_FIELD_N]; /* 工作值(编辑提交后落在这里;保存前不写任何数据源) */
|
||
static uint8_t s_have; /* 1 = 最近一次预填来自一帧有效时标;0 = 无有效时标 */
|
||
static uint8_t s_dirty; /* 1 = 用户改过工作值还没落钟:帧不得覆盖用户输入,
|
||
值列也不写 "--"(否则编辑到一半被 200ms 节拍冲掉) */
|
||
static uint8_t s_editing; /* 1 = 正在编辑(被编辑行 = 当前选中行) */
|
||
static uint8_t s_st; /* 保存态锁存:0 未投过 / 1 OK / 2 ERR */
|
||
static uint8_t s_ed_row; /* 被编辑行号(仅 s_editing=1 时有意义) */
|
||
|
||
static ui_w_editor_t s_ed; /* 数值编辑器实例(本页独占,勿跨页共用) */
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:跟随发布序号取实时帧,并把时标拆成六个工作值
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:uiTask(框架绘制/按键链内)
|
||
* 函数说明:解析失败(空串 = 墙钟无效;或快照里出现非本工程口径的串)一律
|
||
* s_have=0 → 六行显 "--" + 徽标;**不猜**、不回填 0、不回填 1970
|
||
*/
|
||
static void ck_sync(void) {
|
||
uint32_t seq = ui_data_rt_seq();
|
||
ui_data_clock_t f;
|
||
|
||
if ((seq != s_rt_seq) && (ui_data_rt(&s_rt) == 0)) {
|
||
s_rt_seq = seq;
|
||
}
|
||
if ((s_editing != 0u) || (s_dirty != 0u)) {
|
||
return; /* 用户正在改:200 ms 一帧的节拍不得冲掉他还没落钟的输入 */
|
||
}
|
||
if ((s_rt.clock_ok == 0u) || (ui_data_clock_parse(s_rt.clock, &f) != 0)) {
|
||
s_have = 0u;
|
||
return;
|
||
}
|
||
/* 无未落钟改动 ⇒ 值列跟着墙钟走:保存成功后新时标进帧,这就是"回读校验
|
||
* 真读回来了"的屏上证据,不必页面自己造一份"我以为写进去了"的显示 */
|
||
s_val[0] = f.year;
|
||
s_val[1] = f.month;
|
||
s_val[2] = f.day;
|
||
s_val[3] = f.hour;
|
||
s_val[4] = f.minute;
|
||
s_val[5] = f.second;
|
||
s_have = 1u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取当前可编辑性(本次进入是否有修改权限)
|
||
* 入口参数:无
|
||
* 返回值:1 = 可编辑;0 = 只读 uint8_t
|
||
* 限定条件:框架开页之后(init_cb 起,is_editable 读到的就是本次进入的权限)
|
||
* 函数说明:只读进入连保存行都不给(同整定四页的只读分支)——一个按不动的
|
||
* 保存行只是徒增"是不是设备坏了"的误判面
|
||
*/
|
||
static uint8_t ck_editable(void) {
|
||
return (ui_content_page_is_editable() != false) ? 1u : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:总行数(框架 count_cb)
|
||
* 入口参数:无
|
||
* 返回值:6(只读)或 7(可编辑:6 字段 + 保存行) uint16_t
|
||
* 限定条件:框架绘制/翻页链内
|
||
*/
|
||
static uint16_t pg_count(void) {
|
||
return (uint16_t)(CK_FIELD_N + ck_editable());
|
||
}
|
||
|
||
/*
|
||
* 函数功能:逐行取数(框架 refresh)
|
||
* 入口参数:a_row / a_name / a_value - 契约见 ui_content_page.h
|
||
* 返回值:无
|
||
* 限定条件:框架绘制链内
|
||
*/
|
||
static void pg_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||
uint16_t sel = ui_content_page_selected_row();
|
||
|
||
(void)memset(a_value, 0, UI_BUF_STRLEN);
|
||
|
||
if (a_row >= CK_FIELD_N) {
|
||
(void)memcpy(a_name, "保存", sizeof("保存"));
|
||
(void)memcpy(a_value, (s_st == 0u) ? CK_ST_ENTER
|
||
: ((s_st == 1u) ? CK_ST_OK : CK_ST_ERR),
|
||
((s_st == 0u) ? sizeof(CK_ST_ENTER)
|
||
: ((s_st == 1u) ? sizeof(CK_ST_OK) : sizeof(CK_ST_ERR))));
|
||
return;
|
||
}
|
||
|
||
(void)memcpy(a_name, k_ck_name[a_row], strlen(k_ck_name[a_row]) + 1u);
|
||
|
||
if ((s_editing != 0u) && (sel == a_row)) {
|
||
ui_w_editor_format(&s_ed, a_value, UI_BUF_STRLEN); /* 编辑串与光标同规则 */
|
||
return;
|
||
}
|
||
if ((s_have == 0u) && (s_dirty == 0u) && (s_editing == 0u)) {
|
||
(void)memcpy(a_value, CK_INVAL_VAL, sizeof(CK_INVAL_VAL));
|
||
return; /* 墙钟无效且用户还没动手:显 "--",不显 1970、也不显 0 */
|
||
}
|
||
if (a_row == 0u) {
|
||
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%04u", (unsigned int)s_val[0]);
|
||
} else {
|
||
(void)lv_snprintf(a_value, UI_BUF_STRLEN, "%02u", (unsigned int)s_val[a_row]);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页脚带靠左画"无时钟"徽标(墙钟无效的屏上表达)
|
||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||
* 返回值:无
|
||
* 限定条件:框架绘制链内;只在 s_have=0 时落墨
|
||
* 函数说明:UI_COL_FG、不做反显——反显在 1bpp 下表示"选中",徽标不是可选中项;
|
||
* 左边界/所在带见下方注释(实测过标题带会让"无时钟"和标题连成一句)
|
||
*/
|
||
static void ck_draw_badge(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||
lv_area_t area;
|
||
lv_draw_label_dsc_t ld;
|
||
const char *txt = CK_NO_CLOCK_TXT;
|
||
|
||
if ((s_have != 0u) || (s_dirty != 0u)) {
|
||
return; /* 已有有效时标;或用户正在填一个新时间(此时钟确实还没建立,但
|
||
屏上已经在看他输入的数,再压一个徽标只是抢注意力) */
|
||
}
|
||
/* 位置实测两次才定下来,两次踩的坑都记在这里(都是"看起来放得下"):
|
||
* ① 标题带右侧(整定页"未标定"徽标那条线,右界 141):本标题"时钟设置"4 个字
|
||
* 居中占到 x≈104,徽标 36 px 正好贴在它后面,一屏读成"时钟设置无时钟"一句
|
||
* 话——整定页能那么放是因为它最长标题只有 3 个字(到 x≈98)。
|
||
* ② 页脚带左侧 x=7:页脚"第 1 页,共 1 页"**不是居中的**,实测从 x=6 起排,
|
||
* 徽标直接压在"第 1 页"上(像素比对里表现为两边都缺一块)。
|
||
* 现落点 = 清单最后一行(108..121)与页脚带(140 起)之间的空带:谁都不压,
|
||
* 且仍在"这一屏说的是时钟"的视线内 */
|
||
area.x1 = CK_BADGE_X;
|
||
/* 该版本 lv_text_get_width:长度必须传 strlen(传 -1/0 会死循环或返回 0) */
|
||
area.x2 = CK_BADGE_X
|
||
+ (int32_t)lv_text_get_width(txt, (uint32_t)strlen(txt), LV_FONT_DEFAULT, 0);
|
||
area.y1 = CK_BADGE_Y;
|
||
area.y2 = CK_BADGE_Y + UI_TEXT_BAND_H;
|
||
if ((area.x1 > a_clip->x2) || (a_clip->x1 > area.x2) ||
|
||
(area.y1 > a_clip->y2) || (a_clip->y1 > area.y2)) {
|
||
return;
|
||
}
|
||
lv_draw_label_dsc_init(&ld);
|
||
ld.base.layer = a_layer;
|
||
ld.text = txt;
|
||
ld.text_static = 1u;
|
||
ld.color = UI_COL_FG;
|
||
ld.ofs_x = 0;
|
||
ld.ofs_y = 0;
|
||
lv_draw_label(a_layer, &ld, &area);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:自绘(框架 draw_cb 末尾:编辑器光标反显 + 无时钟徽标)
|
||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||
* 返回值:无
|
||
* 限定条件:框架绘制链内
|
||
*/
|
||
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||
if (s_editing != 0u) {
|
||
uint16_t sel = ui_content_page_selected_row();
|
||
int32_t row_top;
|
||
|
||
/* 本页 ≤7 行、rows_per_page = UI_LIST_ROWS(8) ⇒ 恒第 1 页、窗口首行 = 0;
|
||
* 两个变量都只在编辑态有意义,就声明在编辑态这块里(cppcheck variableScope)*/
|
||
row_top = UI_LIST_TOP + ((int32_t)sel * UI_ROW_PITCH);
|
||
ui_w_editor_draw(&s_ed, a_layer, a_clip, row_top, 0, UI_ROW_BAND_H, UI_LIST_RIGHT_X);
|
||
}
|
||
ck_draw_badge(a_layer, a_clip);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:保存——整组校验通过后投递墙钟设置,成功后补投本地操作记录
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:可编辑态、保存行 ENTER;uiTask 上下文
|
||
* 函数说明:三步顺序即判据:① to_epoch 整组校验(组合非法 → 一个字节都不写,
|
||
* 屏上 ERR,光标留在页面上,用户改的就是那一行不必重新找);
|
||
* ② 注入转发(真机 = sys_clock_set_unix + 回读复核;**sys_clock_set()
|
||
* 本身恒返 0**,RTC 写失败只打日志,所以回读是唯一真判据);
|
||
* ③ 只有返 0 才投操作记录——给一次没落地的对时留审计记录,
|
||
* 比少一条记录坏得多
|
||
* 结果落在 s_st 上并**锁存**:状态是全局单值,直读会让用户动一下光标
|
||
* 就看到别处留下的旧结果(契约 §6.3 第 3 条同一条理由)
|
||
*/
|
||
static void ck_save(void) {
|
||
ui_data_clock_t f;
|
||
uint32_t epoch = 0u;
|
||
int rc;
|
||
|
||
f.year = (uint16_t)s_val[0];
|
||
f.month = (uint8_t)s_val[1];
|
||
f.day = (uint8_t)s_val[2];
|
||
f.hour = (uint8_t)s_val[3];
|
||
f.minute = (uint8_t)s_val[4];
|
||
f.second = (uint8_t)s_val[5];
|
||
|
||
if (ui_data_clock_to_epoch(&f, &epoch) != 0) {
|
||
s_st = 2u; /* 组合非法:一条都不投 */
|
||
return;
|
||
}
|
||
rc = ui_data_maint_clock_set(epoch);
|
||
if (rc == 0) {
|
||
s_st = 1u;
|
||
s_dirty = 0u; /* 已落钟:值列交还给实时帧,回读的新时标自己会上屏 */
|
||
(void)ui_data_maint_oper_note(CK_OPER_NOTE);
|
||
} else {
|
||
s_st = 2u;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:开页重置运行态(框架 init_cb)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:框架开页链内(此时本次授予权限已装载,ck_editable 读得到真值)
|
||
*/
|
||
static void pg_init(void) {
|
||
s_rt_seq = 0u;
|
||
s_have = 0u;
|
||
s_dirty = 0u;
|
||
s_st = 0u;
|
||
s_editing = 0u;
|
||
s_ed_row = 0u;
|
||
ui_w_editor_reset(&s_ed);
|
||
(void)memset(s_val, 0, sizeof(s_val));
|
||
ck_sync();
|
||
if (s_have == 0u) {
|
||
/* 无有效时标:起编值取各字段下限(不是 0、也不是 1970)——用户从
|
||
* 2000-01-01 00:00:00 起步改,比从一片 0 里猜要少按十几次键 */
|
||
s_val[0] = (uint16_t)UI_DATA_CLOCK_YEAR_MIN;
|
||
s_val[1] = 1u;
|
||
s_val[2] = 1u;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按键转发(框架 key_cb)
|
||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||
* 返回值:true = 本页已消费;false = 交回框架(↑↓ 移选、← 关页) bool
|
||
* 限定条件:框架按键链内;描述符 edit_capable=1 + key_intercept=1
|
||
* 函数说明:编辑中的 ←→ 是"移数字光标",必须先给页面(key_intercept=1 的用途),
|
||
* 否则框架会把 ← 当成关页,编辑到一半就跳出去;ESC 恒不到这里
|
||
* (框架保证任何一页退得出去);只读进入 ENTER 一律忽略
|
||
*/
|
||
static bool pg_key(lv_key_t a_key) {
|
||
uint16_t sel = ui_content_page_selected_row();
|
||
|
||
if (s_editing != 0u) {
|
||
if (ui_w_editor_key(&s_ed, a_key) == 0u) {
|
||
return false; /* 编辑器不认的键交回框架 */
|
||
}
|
||
if (ui_w_editor_is_editing(&s_ed) == false) {
|
||
/* 编辑器已提交(ENTER 且范围通过):落工作值,作废上一次的保存结果 */
|
||
if (s_ed_row < CK_FIELD_N) {
|
||
s_val[s_ed_row] = (uint16_t)ui_w_editor_value(&s_ed);
|
||
s_dirty = 1u; /* 工作值与墙钟从此两回事,别再让帧覆盖它 */
|
||
}
|
||
s_editing = 0u;
|
||
s_st = 0u; /* 改了任何一位,上一次的保存结果就作废显示 */
|
||
}
|
||
return true;
|
||
}
|
||
|
||
if (a_key != LV_KEY_ENTER) {
|
||
return false; /* ↑↓ 等交回框架内置移选 */
|
||
}
|
||
if (ck_editable() == 0u) {
|
||
return false; /* 只读:不编辑、不保存(保存行本来也不在屏上) */
|
||
}
|
||
if (sel >= CK_FIELD_N) {
|
||
ck_save();
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
/* 进字段编辑:初值取当前工作值;无有效时标时从下限起编 */
|
||
s_ed_row = (uint8_t)sel;
|
||
s_editing = 1u;
|
||
ui_w_editor_begin(&s_ed, (int32_t)s_val[sel], (int32_t)k_ck_min[sel],
|
||
(int32_t)k_ck_max[sel], 0u);
|
||
ui_content_page_invalidate();
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=clock 绑定引用)
|
||
* 列表页骨架(6~7 行 1 页,几何令牌同整定四页)+ 可编辑(edit_capable,
|
||
* 还要本次授予修改权限)+ ← 拦截(编辑中移数字光标)。
|
||
* band_h 必须 >0:selectable 页选中行是黑字,没有白带就是黑底黑字看不见选中行
|
||
*/
|
||
const content_page_def_t ui_page_clock_desc = {
|
||
.id = "clock",
|
||
.title = "时钟设置",
|
||
.selectable = 1u,
|
||
.edit_capable = 1u,
|
||
.key_intercept = 1u,
|
||
.top_y = UI_LIST_TOP,
|
||
.pitch = UI_ROW_PITCH,
|
||
.band_h = UI_ROW_BAND_H,
|
||
.text_dy = 0,
|
||
.left_x = UI_LIST_LEFT_X,
|
||
.right_x = UI_LIST_RIGHT_X,
|
||
.rows_per_page = UI_LIST_ROWS,
|
||
.row_count = CK_ROW_COUNT_MAX,
|
||
.init_cb = pg_init,
|
||
.count_cb = pg_count,
|
||
.refresh = pg_refresh,
|
||
.draw_cb = pg_draw,
|
||
.key_cb = pg_key,
|
||
};
|