sdk: 增量同步主仓新模块(box/anim/scroll/dispatch/listvirt/str/sb_icons/img)

subtree pull sdk 来自主仓 pub/sdk(主仓 commit 68ce46a 等)。新增:
- 布局引擎 meow_ui_box / 动画基座 meow_ui_anim / 滚动物理 meow_ui_scroll
- §6.4 事件分发形式化 / W_LISTVIEW 虚拟化多行 / meow_str 字符串资源 i18n
- sb_icons 动作图标扩充 + topbar 位图图标 / meow_img_downscale
- tools/ 图标生成脚本与 SVG 源
- 对应宿主 test_*.c 与 demo_box_scroll.c

build_sdk.sh 冲突解析:保留公开仓自包含版(vendored meow_font + -Iinclude -I.),
OBJS 手工并入 4 个新模块(meow_ui_box/anim/scroll/str)。保留 vendored meow_font.c/
meow_ipc.h/meow_font.h/meow_notif.h,剔除宿主测试二进制与编译产物。
This commit is contained in:
AskaEth committed 2026-09-02 09:08:51 +08:00
commit 4b094d62a5
56 files changed
+4327 -117

No files matched your search

+174
View File
@@ -0,0 +1,174 @@
/*
* test_meow_ui_str.c — meow_str_* 宿主单测(2026-09-02,§13.i18n 雏形)。
*
* 编译(宿主,meow_ui_str 为纯 libc,无需 SDK 其它对象):
* gcc -Wall -Wextra -o test_meow_ui_str test_meow_ui_str.c meow_ui_str.c -I.
*
* 两种模式(覆盖表按 locale 懒加载,分两次跑):
* ./test_meow_ui_str tmp → unsetenv MEOW_DATA_DIR,仅验证内嵌双语表(无覆盖表)
* ./test_meow_ui_str env → 设 MEOW_DATA_DIR=<mkdtemp>/data,写 <mkdtemp>/strings/en_us.txt,
* 验证 TF 覆盖表优先级(覆盖>内嵌)+ locale 即时生效
*
* 覆盖矩阵:
* T1 内嵌回退(默认 zh_cn):common.ok/loading/no_data 等取 zh
* T2 切 locale 即时生效:set_locale(en_us) → 内嵌 en;set_locale(zh_cn) → 内嵌 zh(无需重初始化)
* T3 未知 id 回退:meow_str("nope.missing") 原样返回 id;meow_str(NULL) 返回 ""
* T4 TF 覆盖优先级:override > 内嵌当前语言;未覆盖 id 回落内嵌;自定义 id 命中
* T5 查找正确性:一批 id 的 zh/en 值逐一断言
* T6 meow_strf:取串并按 snprintf 语义写调用方缓冲(与 meow_str 一致)
*
* 注:set_locale 的 ui.locale 持久化目标是板载 /usr/data/meow/settings.conf,宿主无该目录
* → 静默失败(运行时仍切换,仅不跨启动);不影响本测试断言。
*/
#include "meow_ui.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
static int g_fail = 0;
#define CHECK(cond, msg) do { \
if (cond) { printf(" PASS: %s\n", msg); } \
else { printf(" FAIL: %s\n", msg); g_fail = 1; } \
} while (0)
/* 找内嵌某 id 的 zh/en 期望值(供 T5 逐项对照)。不在表内返回 NULL。 */
static const char *expect(const char *id, int en)
{
/* 与 meow_ui_str.c 的 g_embed[] 对齐(此处只列 T5 抽查的子集) */
static const struct { const char *id; const char *zh; const char *en; } t[] = {
{ "common.ok", "确定", "OK" },
{ "common.cancel", "取消", "Cancel" },
{ "common.back", "返回", "Back" },
{ "common.confirm", "确认", "Confirm" },
{ "common.loading", "加载中...", "Loading..." },
{ "common.no_data", "暂无数据", "No data" },
{ "common.retry_fail", "重试失败", "Retry failed" },
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
{ "ime.space", "空格", "Space" },
{ "ime.enter", "确定", "OK" },
{ "ime.esc", "取消", "Cancel" },
{ "ime.clear", "清", "Clear" },
{ "ime.pinyin_mode", "拼音", "Pinyin" },
{ "ime.no_candidate", "(无候选)", "(none)" },
};
for (size_t i = 0; i < sizeof t / sizeof t[0]; i++)
if (strcmp(t[i].id, id) == 0) return en ? t[i].en : t[i].zh;
return NULL;
}
int main(int argc, char **argv)
{
const char *mode = (argc > 1) ? argv[1] : "tmp";
char *tmpdir = NULL;
if (strcmp(mode, "env") == 0) {
/* 建 <mkdtemp>/data + <mkdtemp>/strings/en_us.txt(meow_str 由 MEOW_DATA_DIR 去尾 /data
* 得 <mkdtemp>/,再拼 strings/<locale>.txt)。 */
tmpdir = strdup("/tmp/meowstrtestXXXXXX");
if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; }
char datadir[512], strdir[512];
snprintf(datadir, sizeof datadir, "%s/data", tmpdir);
snprintf(strdir, sizeof strdir, "%s/strings", tmpdir);
mkdir(datadir, 0755);
mkdir(strdir, 0755);
char file[512];
snprintf(file, sizeof file, "%s/strings/en_us.txt", tmpdir);
FILE *f = fopen(file, "w");
if (!f) { printf("FATAL: cannot write override file\n"); free(tmpdir); return 2; }
/* 混合:正常行 / '=' 两侧带空格 / 空行 / 无 '=' 行(应被跳过) */
fprintf(f, "# comment line (no equals)\n");
fprintf(f, "common.ok=确认OK\n");
fprintf(f, "common.custom=自定义覆盖\n");
fprintf(f, "common.loading = 加载中覆盖\n");
fprintf(f, "badline-without-equals\n");
fprintf(f, "\n");
fclose(f);
setenv("MEOW_DATA_DIR", datadir, 1);
} else {
unsetenv("MEOW_DATA_DIR");
}
printf("== meow_str test [%s] ==\n", mode);
printf(" locale = %s\n", meow_str_locale());
/* --- T1 内嵌回退(默认 zh_cn,无覆盖表/无 zh_cn.txt)--- */
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T1 zh common.ok = 确定");
CHECK(strcmp(meow_str("common.cancel"), "取消") == 0, "T1 zh common.cancel = 取消");
CHECK(strcmp(meow_str("common.loading"), "加载中...") == 0, "T1 zh common.loading = 加载中...");
CHECK(strcmp(meow_str("common.no_data"), "暂无数据") == 0, "T1 zh common.no_data = 暂无数据");
CHECK(strcmp(meow_str("ime.space"), "空格") == 0, "T1 zh ime.space = 空格");
/* --- T3 未知 id 回退(永不 NULL)--- */
CHECK(strcmp(meow_str("nope.missing"), "nope.missing") == 0, "T3 未知 id 原样返回 id");
CHECK(strcmp(meow_str(NULL), "") == 0, "T3 meow_str(NULL) = \"\"");
/* --- T6 meow_strf:与 meow_str 一致,snprintf 语义 --- */
{
char buf[128];
int r = meow_strf(buf, sizeof buf, "common.ok");
CHECK(r == (int)strlen("确定") && strcmp(buf, "确定") == 0, "T6 meow_strf zh common.ok = 确定");
meow_strf(buf, sizeof buf, "common.no_data");
CHECK(strcmp(buf, meow_str("common.no_data")) == 0, "T6 meow_strf == meow_str");
CHECK(meow_strf(NULL, 10, "common.ok") == -1, "T6 meow_strf(out=NULL) = -1");
CHECK(meow_strf(buf, 0, "common.ok") == -1, "T6 meow_strf(outsz=0) = -1");
}
/* --- T2 切 locale 即时生效(无需重初始化)---
* 用「未在覆盖表」的 id 证明 locale 即时切换(common.ok 等在 env 会被覆盖,留给 T4 验覆盖路径)。 */
meow_str_set_locale("en_us");
CHECK(strcmp(meow_str_locale(), "en_us") == 0, "T2 locale = en_us");
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T2 en common.cancel = Cancel");
CHECK(strcmp(meow_str("common.no_data"), "No data") == 0, "T2 en common.no_data = No data");
CHECK(strcmp(meow_str("ime.space"), "Space") == 0, "T2 en ime.space = Space");
/* --- T4 TF 覆盖优先级(仅 env 模式)--- */
if (strcmp(mode, "env") == 0) {
/* ① 覆盖内嵌当前语言(en_us*下 common.ok 应是覆盖值"确认OK"而非内嵌"OK") */
CHECK(strcmp(meow_str("common.ok"), "确认OK") == 0, "T4 @en override 覆盖 common.ok");
/* ② 覆盖自定义 id(内嵌无 → 覆盖命中) */
CHECK(strcmp(meow_str("common.custom"), "自定义覆盖") == 0, "T4 @en override 自定义 id");
/* ③ '=' 两侧带空格也可解析 */
CHECK(strcmp(meow_str("common.loading"), "加载中覆盖") == 0, "T4 @en override 带空格行");
/* ④ 未覆盖 id 回落内嵌当前语言 */
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T4 @en 未覆盖 → 内嵌 en");
CHECK(strcmp(meow_str("ime.clear"), "Clear") == 0, "T4 @en ime.clear 未覆盖 → 内嵌 en");
/* ⑤ 自定义 id 在 zh(无 zh_cn.txt,覆盖表清空)→ 内嵌无 → 原样返回 id */
meow_str_set_locale("zh_cn");
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T4 @zh 覆盖清除 → 内嵌 zh");
CHECK(strcmp(meow_str("common.custom"), "common.custom") == 0, "T4 @zh 自定义 id 原样回退");
meow_str_set_locale("en_us"); /* 回到 en 供 T5 */
}
/* --- T5 查找正确性:一批 id 的 zh/en 逐一断言(仅 tmp 模式——无覆盖干扰,
* 才能验证内嵌表本身;env 模式的内嵌 vs 覆盖已由 T2/T4 覆盖,此处跳过多余断言)--- */
if (strcmp(mode, "tmp") == 0) {
const char *ids[] = { "common.ok", "common.cancel", "common.back", "common.confirm",
"common.loading", "common.no_data", "common.retry_fail",
"common.tap_ok_edit", "ime.space", "ime.enter", "ime.esc",
"ime.clear", "ime.pinyin_mode", "ime.no_candidate" };
meow_str_set_locale("zh_cn");
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
if (expect(ids[i], 0)) {
char msg[128];
snprintf(msg, sizeof msg, "T5 zh %s == 期望", ids[i]);
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 0)) == 0, msg);
}
}
meow_str_set_locale("en_us");
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
if (expect(ids[i], 1)) {
char msg[128];
snprintf(msg, sizeof msg, "T5 en %s == 期望", ids[i]);
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 1)) == 0, msg);
}
}
}
if (tmpdir) free(tmpdir);
if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; }
printf("\nRESULT: ALL PASS\n");
return 0;
}