#include "ui.h" #include "database.h" #include "language.h" #include "../Drivers/lcd.h" #include "../Drivers/adc.h" // 引入外部主菜单对码项名称的字符串 ID 表 (例如门磁/PIR/烟感等防区类型) extern const StringID device_menu_ids[5]; // 底部日期栏固定用 8x16 小字体、LCD_ShowStringCentered 居中绘制在 y=195; // 编辑日期时对 日/月/年 做局部刷新需要精确知道每个字段的像素 X 坐标,这里按 // LCD_ShowStringCentered 的居中公式 (LCD_WIDTH - 字符数*8) / 2 手工算好, // 前提是字符串格式固定为 "WWW DD-MM-YY" (12 个字符),不能随意改动格式。 #define DATE_BAR_Y 195 #define DATE_BAR_START_X 19 // (135 - 12*8) / 2 = 19 #define DATE_BAR_DAY_X (DATE_BAR_START_X + 4 * 8) // 第 4 个字符起 = 51 #define DATE_BAR_MONTH_X (DATE_BAR_START_X + 7 * 8) // 第 7 个字符起 = 75 #define DATE_BAR_YEAR_X (DATE_BAR_START_X + 10 * 8) // 第 10 个字符起 = 99 #define DATE_BAR_FIELD_W 16 // 2 位数字,8px/字符 /** * @brief 拼出"星期缩写 日-月"格式的日期字符串 (如 "VEN 10-07"),供 HOME 页时钟显示 * @param out 输出缓冲区,调用方需保证至少 10 字节 (3字母+空格+2位+'-'+2位+'\0') */ static void Build_DateString(char *out, u8 weekday, u8 day, u8 month) { char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday)); u8 i = 0; while (*wd) { out[i++] = *wd++; } out[i++] = ' '; out[i++] = '0' + (day / 10); out[i++] = '0' + (day % 10); out[i++] = '-'; out[i++] = '0' + (month / 10); out[i++] = '0' + (month % 10); out[i] = '\0'; } /** * @brief 拼出"星期缩写 日-月-年"格式的日期字符串 (如 "VEN 10-07-25"),供时间设置页编辑日期时显示 * @param out 输出缓冲区,调用方需保证至少 13 字节 (3字母+空格+2位+'-'+2位+'-'+2位+'\0') */ static void Build_DateStringWithYear(char *out, u8 weekday, u8 day, u8 month, u8 year) { char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday)); u8 i = 0; while (*wd) { out[i++] = *wd++; } out[i++] = ' '; out[i++] = '0' + (day / 10); out[i++] = '0' + (day % 10); out[i++] = '-'; out[i++] = '0' + (month / 10); out[i++] = '0' + (month % 10); out[i++] = '-'; out[i++] = '0' + (year / 10); out[i++] = '0' + (year % 10); out[i] = '\0'; } /** * @brief 渲染并显示系统待机时钟主页面 * @param state 当前的系统布防状态 (STATE_ARMED: 已布防, 否则为正常或撤防) * @param hour 当前待显示的小时数 (0~23) * @param min 当前待显示的分钟数 (0~59) * @param weekday 当前星期 (0=周日, ..., 6=周六) * @param day 当前日 (1~31) * @param month 当前月 (1~12) * @details 该页面被划分为三大功能视觉区: * 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示真实剩余电量百分比以及电池单色位图。 * 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。 * 3. 底部信息栏:使用灰色小字模绘制真实星期日期 "VEN 10-07" (星期五 7月10日)。 */ void UI_ShowClockPage(SystemState state, u8 hour, u8 min, u8 weekday, u8 day, u8 month) { char time_str[6]; char date_str[10]; char batt_str[7]; u8 batt; // 清屏,底色填充为纯黑 LCD_Clear(COLOR_BLACK); // ====== 绘制左上角布撤防图标 ====== if (state == STATE_ARMED) { // 状态: ARMED (已布防) -> 绘制红色闭合锁头 LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形 LCD_DrawRectBorder(14, 15, 12, 9, COLOR_RED); // 闭合状态的拱形锁环 LCD_FillRect(18, 28, 4, 5, COLOR_RED); // 锁芯暗扣 } else { // 状态: NORMAL / DISARMED (非布防) -> 绘制绿色开启锁头 LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); // 锁身矩形 LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); // 左锁环 LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); // 锁环顶 LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环 } // ====== 绘制右上角真实电池电量 ====== batt = ADC_GetBatteryPercent(); if (batt >= 100) { batt_str[0] = '1'; batt_str[1] = '0'; batt_str[2] = '0'; batt_str[3] = '%'; batt_str[4] = '\0'; } else { batt_str[0] = '0' + (batt / 10); batt_str[1] = '0' + (batt % 10); batt_str[2] = '%'; batt_str[3] = '\0'; } // 充电中则在百分比后追加 '+' 后缀 if (Is_Charging()) { u8 len = (batt >= 100) ? 4 : 3; batt_str[len] = '+'; batt_str[len + 1] = '\0'; } LCD_ShowString(70, 24, batt_str, COLOR_GRAY, COLOR_BLACK); // 渲染真实动态百分比 LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池图标 // ====== 绘制中部时间 "HH:MM" ====== time_str[0] = '0' + (hour / 10); // 小时十位 time_str[1] = '0' + (hour % 10); // 小时个位 time_str[2] = ':'; // 冒号分隔符 time_str[3] = '0' + (min / 10); // 分钟十位 time_str[4] = '0' + (min % 10); // 分钟个位 time_str[5] = '\0'; // 字符串终止字符 LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); // 使用 16x32 特大字体居中绘制时间 // ====== 绘制 AM/PM 状态 ====== LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK); // ====== 绘制日期星期栏 (真实日期,不再是写死的占位文案) ====== Build_DateString(date_str, weekday, day, month); LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK); } /** * @brief 渲染显示传感器对码二级菜单页面 * @param selected_index 当前聚焦选中的菜单行索引 (0~4,分别对应 5 种传感器) * @details 采用典型的纵向菜单滚轮特效: * - 中间 110 行高度为当前选中行,使用青色框高亮圈起并用青色绘制名称。 * - 上下邻近行显示为暗灰色,且无边框保护,营造层次感。 */ void UI_ShowPairMenuPage(u8 selected_index) { LCD_Clear(COLOR_BLACK); // 绘制标题 "MENU APPAI." (配对/对码设置菜单) LCD_ShowStringCentered(30, Lang_Get(STR_PAIR_MENU_TITLE), COLOR_AMBER, COLOR_BLACK); // 渲染上方相邻项 (如果当前选择的不是第一行) if (selected_index > 0) { LCD_ShowStringCentered(78, Lang_Get(device_menu_ids[selected_index - 1]), COLOR_GRAY, COLOR_BLACK); } // 渲染当前选中的高亮中间项 LCD_DrawRectBorder(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中) LCD_ShowStringCentered(118, Lang_Get(device_menu_ids[selected_index]), COLOR_CYAN, COLOR_BLACK); // 渲染下方相邻项 (如果当前选择的不是最后一行) if (selected_index < 4) { LCD_ShowStringCentered(158, Lang_Get(device_menu_ids[selected_index + 1]), COLOR_GRAY, COLOR_BLACK); } } /** * @brief 渲染正在搜索配对信号的雷达扫描等待页面 * @param frame_index 当前的雷达波纹帧索引号 (0~2 帧循环) * @details 在中央以青色渲染雷达同心圆扩散动图,并在底部提示 "DECLENCH. CAPT" (请触发传感器发射) */ void UI_ShowPairWaitPage(u8 frame_index) { LCD_Clear(COLOR_BLACK); // 绘制头部 "APPAIRAGE" (对码中) LCD_ShowStringCentered(35, Lang_Get(STR_PAIRING_TITLE), COLOR_GRAY, COLOR_BLACK); // 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51) if (frame_index == 0) LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK); else if (frame_index == 1) LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK); else LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK); // 绘制状态描述 LCD_ShowStringCentered(165, Lang_Get(STR_PAIR_SEARCHING), COLOR_WHITE, COLOR_BLACK); // 搜寻中 LCD_ShowStringCentered(200, Lang_Get(STR_PAIR_TRIGGER), COLOR_CYAN, COLOR_BLACK); // 请触发传感器以发射射频信号 } /** * @brief 渲染捕获到传感器地址后的微调确认页面 * @param type 已解码出的传感器硬件类型 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸) * @param zone_index 当前配置的防区编号 (1~99),由用户上下键修改微调 */ void UI_ShowPairConfirmPage(u8 type, u8 zone_index) { char zone_str[3]; LCD_Clear(COLOR_BLACK); // 顶部显示捕获回馈标志 "v RECU" (信号已接收) LCD_ShowStringCentered(32, Lang_Get(STR_PAIR_RECEIVED), COLOR_GREEN, COLOR_BLACK); // 在上部居中绘制设备类别所对应的图形 (135px 下居中 X = 51) if (type == 0) { LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标 } else if (type == 2 || type == 3) { LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标 } else { LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标 } // 渲染传感器类型的文字 LCD_ShowStringCentered(105, Lang_Get(sensor_type_name_ids[type]), COLOR_WHITE, COLOR_BLACK); // 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (在 135px 下居中 X=22, 宽度 90) LCD_DrawRectBorder(22, 140, 90, 44, COLOR_CYAN); zone_str[0] = '0' + (zone_index / 10); zone_str[1] = '0' + (zone_index % 10); zone_str[2] = '\0'; LCD_ShowString16x32(51, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号 (135px 下居中 X = 51) // 底部提示按键动作 LCD_ShowStringCentered(215, Lang_Get(STR_PAIR_CONFIRM), COLOR_GRAY, COLOR_BLACK); // 短按以确认保存 } /** * @brief 渲染显示配对成功反馈页 */ void UI_ShowPairSuccessPage(void) { LCD_Clear(COLOR_BLACK); // 渲染 32x32 对勾图标 (135px 下居中 X = 51) LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK); LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_SUCCESS), COLOR_GREEN, COLOR_BLACK); // 成功 LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_SAVED), COLOR_GRAY, COLOR_BLACK); // 配置已保存 } /** * @brief 渲染显示配对失败页面 * @param is_timeout 1表示超时退出的失败,0表示其他对码错误 */ void UI_ShowPairFailPage(u8 is_timeout) { LCD_Clear(COLOR_BLACK); // 渲染 32x32 红色叉号图标 (135px 下居中 X = 51) LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_FAILED), COLOR_RED, COLOR_BLACK); // 失败 if (is_timeout) { LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_TIMEOUT), COLOR_GRAY, COLOR_BLACK); // 对码超时 } else { LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_WRONG_TYPE), COLOR_GRAY, COLOR_BLACK); // 传感器类型不符 } } /** * @brief 渲染显示传感器入侵警报页面 * @param type 触发报警的传感器类型 (0~4) * @param zone_index 报警传感器的防区号 (1~99, 0) * @details 在界面外侧四周圈起醒目的双重红色警告边框 (COLOR_RED), * 中间绘制设备对应的大警告图标,并在下方用特大红字标明防区及设备类型(如 "PORTE 02"), * 最下方渲染白色粗体 "INTRUSION!" (入侵警告) 提示。 */ void UI_ShowAlarmPage(u8 type, u8 zone_index) { char zone_info[20]; LCD_Clear(COLOR_BLACK); // 绘制双重红色爆闪粗边界框 (在新宽 135px 下对称扩展) LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED); LCD_DrawRectBorder(6, 6, 123, 228, COLOR_RED); // 绘制报警设备的图形 (135px 下居中 X = 51) if (type == 0) { LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警 } else if (type == 2 || type == 3) { LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警 } else { LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警 } // 拼接字符串: "设备类型名 两位防区号" { u8 len = 0; char *p = Lang_Get(sensor_type_name_ids[type]); while(*p) { zone_info[len++] = *p++; } zone_info[len++] = ' '; zone_info[len++] = '0' + (zone_index / 10); zone_info[len++] = '0' + (zone_index % 10); zone_info[len] = '\0'; } LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK); // 红字绘制设备防区 LCD_ShowStringCentered(175, Lang_Get(STR_ALARM_INTRUSION), COLOR_WHITE, COLOR_BLACK); // 白字入侵提示 } /** * @brief 渲染显示 SOS 主动求救发送状态页面 * @details 绘制红、白双色三层粗边界警告,中央绘制细致的警徽警灯图案, * 绘制红色闪烁求助状态字样 "SOS ENVOI" (求救发送中...) 和 "APPEL..." (呼叫中)。 */ void UI_ShowSosPage(void) { LCD_Clear(COLOR_BLACK); // 绘制红白双层紧急警示框 (在新宽 135px 下对称扩展) LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED); LCD_DrawRectBorder(5, 5, 125, 230, COLOR_RED); LCD_DrawRectBorder(8, 8, 119, 224, COLOR_WHITE); // ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 (在新宽 135px 下整体右移 7 像素) ====== LCD_FillRect(51, 45, 32, 2, COLOR_RED); // 警灯顶部 LCD_FillRect(55, 47, 24, 12, COLOR_RED); // 警灯红色灯罩 LCD_FillRect(47, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座 LCD_FillRect(64, 51, 6, 6, COLOR_WHITE); // 中间高亮光影 LCD_FillRect(65, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光 LCD_FillRect(53, 38, 4, 4, COLOR_RED); // 左侧散射闪光 LCD_FillRect(77, 38, 4, 4, COLOR_RED); // 右侧散射闪光 // 绘制求救中文字样 LCD_ShowStringCentered(120, Lang_Get(STR_SOS_TITLE), COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(150, Lang_Get(STR_SOS_SENDING), COLOR_RED, COLOR_BLACK); // 求救中 LCD_ShowStringCentered(195, Lang_Get(STR_SOS_CALLING), COLOR_WHITE, COLOR_BLACK); // 紧急呼叫中 } /** * @brief 渲染显示与网络摄像机 (IPC) 进行一键绑定配对的进度画面 * @param addr 手环自身的 24 位克隆出厂硬件地址 */ void UI_ShowBindingPage(u32 addr) { char id_str[20]; LCD_Clear(COLOR_BLACK); // ====== 像素级拼装绘制一个青色网络摄像头简图 ====== LCD_DrawRectBorder(43, 46, 32, 26, COLOR_CYAN); // 相机主体框 (135px 下居中等比右移 X = 43) LCD_FillRect(51, 56, 6, 6, COLOR_CYAN); // 镜头光圈点 (135px 下居中等比右移 X = 51) LCD_FillRect(75, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈 (135px 下居中等比右移 X = 75) LCD_FillRect(77, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1 (135px 下居中等比右移 X = 77) LCD_FillRect(79, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2 (135px 下居中等比右移 X = 79) LCD_ShowStringCentered(120, Lang_Get(STR_LINK_TITLE), COLOR_WHITE, COLOR_BLACK); // 连接/绑定 LCD_ShowStringCentered(155, Lang_Get(STR_BIND_SENDING), COLOR_CYAN, COLOR_BLACK); // 发送对码帧中 // 拼接要对外广播的手环 24 位克隆物理 ID 十六进制字符串并显示在最下方 id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' '; id_str[4] = '0'; id_str[5] = 'x'; { u8 val1 = (u8)(addr >> 16); u8 val2 = (u8)(addr >> 8); u8 val3 = (u8)addr; u8 nibble = val1 >> 4; id_str[6] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); nibble = val1 & 0x0F; id_str[7] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); nibble = val2 >> 4; id_str[8] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); nibble = val2 & 0x0F; id_str[9] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); nibble = val3 >> 4; id_str[10] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); nibble = val3 & 0x0F; id_str[11] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); id_str[12] = '\0'; } LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK); } /** * @brief 渲染显示设置主菜单界面 * @param selected_index 当前菜单高亮行索引 (0:语言切换, 1:时钟修改, 2:对码匹配, 3:摄像机绑定) * @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形, * 并在下方打印对应当前语言的文字描述。 */ void UI_ShowMainMenu(u8 selected_index) { LCD_Clear(COLOR_BLACK); // ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ====== LCD_FillRect(65, 20, 4, 2, COLOR_GRAY); LCD_FillRect(63, 22, 8, 2, COLOR_GRAY); LCD_FillRect(61, 24, 12, 2, COLOR_GRAY); if (selected_index == 0) { // 菜单 0:语言切换。用当前语言的两字母缩写代替图标,直接展示切换后的效果 LCD_ShowString16x32Centered(75, (Lang_Current() == LANG_FR) ? "FR" : "EN", COLOR_CYAN, COLOR_BLACK); LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LANGUAGE), COLOR_WHITE, COLOR_BLACK); } else if (selected_index == 1) { // 菜单 1:时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51) LCD_DrawRectBorder(51, 70, 32, 32, COLOR_CYAN); // 闹钟外圈 LCD_FillRect(66, 76, 2, 10, COLOR_CYAN); // 闹钟时针 LCD_FillRect(66, 85, 8, 2, COLOR_CYAN); // 闹钟分针 LCD_ShowStringCentered(150, Lang_Get(STR_MENU_HORLOGE), COLOR_WHITE, COLOR_BLACK); // 时钟/时间修改 } else if (selected_index == 2) { // 菜单 2:无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51) LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK); LCD_ShowStringCentered(150, Lang_Get(STR_MENU_APPAIRAGE), COLOR_WHITE, COLOR_BLACK); // 配对 } else if (selected_index == 3) { // 菜单 3:网络摄像机绑定。中部绘制一个相机/联通关系图 (135px 下居中等比右移 X = 57) LCD_DrawRectBorder(57, 74, 20, 16, COLOR_CYAN); LCD_FillRect(65, 80, 4, 4, COLOR_CYAN); LCD_FillRect(77, 76, 2, 12, COLOR_CYAN); LCD_FillRect(79, 78, 2, 8, COLOR_CYAN); LCD_FillRect(81, 80, 2, 4, COLOR_CYAN); LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LIAISON), COLOR_WHITE, COLOR_BLACK); // 摄像机绑定 } // ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ====== LCD_FillRect(61, 210, 12, 2, COLOR_GRAY); LCD_FillRect(63, 212, 8, 2, COLOR_GRAY); LCD_FillRect(65, 214, 4, 2, COLOR_GRAY); } /** * @brief 渲染修改时间/日期闪烁调节页面 * @param hour 修改状态中的小时 * @param min 修改状态中的分钟 * @param day 修改状态中的日 * @param month 修改状态中的月 * @param year 修改状态中的年 (0~99,代表 2000~2099) * @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六) * @param selection 当前正在编辑聚焦的属性位 (1:小时, 2:分钟, 3:日, 4:月, 5:年) * @param blink_on 当前 500ms 控制隐现的闪烁周期标志位 (1-绘制正常字,0-以空格消隐该数值字符) */ // 小时/分钟数字在屏幕上的固定坐标 (16x32 字体,两位数字宽度为 32px) #define SET_TIME_DIGIT_Y 100 #define SET_TIME_DIGIT_W 32 #define SET_TIME_DIGIT_H 32 #define SET_TIME_HOUR_X 23 #define SET_TIME_MIN_X 75 void UI_ShowSetTimePage(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on) { char time_str[6]; char date_str[13]; LCD_Clear(COLOR_BLACK); // 绘制绿锁头等背景静态信息 LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 移动电池百分比和图标到 135px 右上角 LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK); LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 整页绘制时,各数字一律完整显示 (消隐闪烁效果交给 UI_UpdateSetTimeDigit 局部刷新处理) time_str[0] = '0' + (hour / 10); time_str[1] = '0' + (hour % 10); time_str[2] = '\0'; LCD_ShowString16x32(SET_TIME_HOUR_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时 (在 135px 下整体居中 X = 23) // 绘制中间冒号 (在 135px 下整体居中 X = 59) LCD_ShowString16x32(59, 98, ":", COLOR_WHITE, COLOR_BLACK); time_str[0] = '0' + (min / 10); time_str[1] = '0' + (min % 10); time_str[2] = '\0'; LCD_ShowString16x32(SET_TIME_MIN_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟 (在 135px 下整体居中 X = 75) // 补齐底部的固定辅助元素 LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK); // 编辑日期时,日期栏额外带上年份 ("WWW DD-MM-YY"),方便看清正在改的年份数值 Build_DateStringWithYear(date_str, weekday, day, month, year); LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK); } /** * @brief 局部刷新时间设置界面里"正在调节的那一个"字段 (小时/分钟/日/月/年) * @param selection 1:小时, 2:分钟, 3:日, 4:月, 5:年 * @param blink_on 1:显示数值, 0:消隐(涂黑),用于 500ms 周期闪烁效果 * @details 小时/分钟用 32x32px 大字体区域;日/月/年用日期栏里对应的 16x16px 小字体区域, * 都只重画自己那一小块,不触碰锁头/电池/冒号/AM-PM/星期文字等其它内容, * 避免每次闪烁或调值都整屏 LCD_Clear() 造成全屏闪烁。 */ void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 selection, u8 blink_on) { char buf[3]; u16 x; u16 y; u16 w; u16 h; u8 value; switch (selection) { case 1: x = SET_TIME_HOUR_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = hour; break; case 2: x = SET_TIME_MIN_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = min; break; case 3: x = DATE_BAR_DAY_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = day; break; case 4: x = DATE_BAR_MONTH_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = month; break; default: x = DATE_BAR_YEAR_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = year; break; } if (!blink_on) { // 灭周期:直接把这一小块涂黑,形成消隐效果 LCD_FillRect(x, y, w, h, COLOR_BLACK); return; } buf[0] = '0' + (value / 10); buf[1] = '0' + (value % 10); buf[2] = '\0'; if (selection <= 2) { LCD_ShowString16x32(x, y, buf, COLOR_CYAN, COLOR_BLACK); } else { LCD_ShowString(x, y, buf, COLOR_GRAY, COLOR_BLACK); } }