diff --git a/APPS.md b/APPS.md index ea8b881..2bede4d 100644 --- a/APPS.md +++ b/APPS.md @@ -68,10 +68,12 @@ | `exec` | ✓ | 应用可执行文件绝对路径(必须是**静态** MIPS32r2 ELF,soft/hard-float 均可) | | `package` | — | 反域名权威标识(如 `os.meow.hello`),launcher 取之为 app_id;**目录名=package**。缺省回退目录名 `de->d_name`;两者存在且不同 → WARN | | `version` / `author` | — | 保留字段,v2 不解析 | +| `fullscreen` | — | app 级全屏:`1`=进入全屏(状态栏**收合至提示线** + **保留左缘返回/顶部提示线/下滑通知中心**,见 MEOWOS_SPEC_REF §5.1);activity 级用它 `route..fullscreen`。**app.json `fullscreen` 字段此前未解析/已定未做 → 本次实现**(launcher 启动/切 activity 据声明预设 SDK 全屏,env `MEOW_FULLSCREEN`)[源:用户 2026-08-30] | +| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(app 需大字库时可声明 `"full"`,默认小字库 `common`≈4k;launcher 据此为该 app 加载大字库 `gbk`≈20k)。**注意**:与 `"fonts":[""]`(应用自带字库句柄数组)**同名二义**,见 MEOWOS_SPEC_REF §7 A-14 [源:用户 2026-08-30] | - 目录扫描深度 1 级:只枚举 `MeowAPPs/` 下的直接子目录 - 应用计数上限 **8**(超出忽略;单屏 8 行) -- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.`(keep_awake)/service.),不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30] +- app.json 解析器极简(launcher 内实现):仅匹配 `"name":`、`"exec":`、`"package":` 后的引号字符串(及扩展 icon/framework/immersive/route./`awake.`(keep_awake)/service.),**`fullscreen`(本次实现)与 `fonts`(档位串) 已解析**,不做 JSON 校验(多行/单行均可)[源:审计 2026-08-30][源:用户 2026-08-30] - 列表刷新时机:每次菜单打开时重新扫描(TF 卡热插拔后立即可见) ### 2.2 边界情况行为(launcher 实现,全部真机验证) diff --git a/NOTIFICATION_SYSTEM.md b/NOTIFICATION_SYSTEM.md index 4400910..311c8cf 100644 --- a/NOTIFICATION_SYSTEM.md +++ b/NOTIFICATION_SYSTEM.md @@ -36,7 +36,7 @@ target_activity(可选, 来自 hints) / progress(可选 int) ## 5. 存储分层(用户已定 2026-08-29) - **关键配置优先板载**:设置类(notify.* 配额等)存板载 `/usr/data/meow/settings.conf`(B2a),确保**无卡时系统正常工作**。 - **实时通知 → 本地内存**(板载,动态分配/ring)。 -- **超时/转储 → TF 卡** `MeowOS/notifications/`(**系统约定:系统文件统一放 MeowOS/ 下**,非 MeowData/——MeowData 是应用专属数据根)(转储区,如 notifications/.json 或 appended 日志)。 +- **超时/转储 → TF 卡** `MeowOS/notifications/`(**系统约定:系统文件统一放 MeowOS/ 下**,非 MeowData/——MeowData 是应用专属数据根)(转储区,**appended 日志行 CSV**,见 §8)。 - **无 TF 卡可运行**(本地内存态即可,不依赖卡;TF 卡在时才转储)。转储/日志类是可降级数据(无卡用 /tmp 回退),配置类永远板载。 - **本地数据清理策略**: - 关机/重启前 → 清理本地内存通知数据(正常关机路径)。 @@ -60,6 +60,8 @@ target_activity(可选, 来自 hints) / progress(可选 int) - **正常电源事件(关机/重启)触发清除板载通知数据之前**:先转储所有未上卡的通知,再清除板载。 - **异常关机**:开机时清理板载残留通知数据。 - TF 卡转储区 `MeowOS/notifications/`:保留**最新 10MB**(ring/截断),**不被电源事件清理**(持久留存)。 +- **转储格式(定案 #6,M2.5x)**:`MeowOS/notifications/` 写为 **appended 日志行**(每条通知一行,CSV 风格:`id,app_id,分类,标题,时间,…`),**按总大小滚动截断**(保留最新 **10MB**)。与既有「10s 转储」衔接:创建 10s 后即按此格式追加一行 `[源:用户 2026-08-30]`。 +- **启动时清板载 + 清内存通知表**:开机(含异常关机后)清理板载残留通知数据,并**清空内存通知表**(在既有「异常关机:开机时清理板载残留」基础上**补充内存表一并清**);正常关机/重启前先转储未上卡通知再清板载(沿用既有规则)`[源:用户 2026-08-30]`。 - **无 TF 卡**:系统可运行(内存态通知即可),不依赖卡。 ## 9. 通知中心 UI(阶段 B) diff --git a/SYSTEM_SETTINGS.md b/SYSTEM_SETTINGS.md index 1a9e3bf..82df6dd 100644 --- a/SYSTEM_SETTINGS.md +++ b/SYSTEM_SETTINGS.md @@ -42,6 +42,7 @@ | | `font.class_latin` | path | *(空=跟全局)* | 多字体:A-Za-z 所用字体 | | | `font.class_latin_sym` | path | *(空=跟全局)* | 多字体:半角标点 所用字体 | | | `font.class_other` | path | *(空=跟全局)* | 多字体:其他字符 所用字体 | +| | `font.load_full` | int 0/1 | 0 | **加载大字库(M2.5x)**:1=**强制全系统用大字库(`gbk`≈20k 档)**,覆盖 app 未声明/覆盖默认 `font.file`;0=默认小字库(`common`≈4k),仅 app 声明 `fonts:"full"` 才大字库 [源:用户 2026-08-30] | | | `font.size.title` / `font.size.body` | int | 28/20 | 标题/正文字号 | | | `ui.style` | enum | `md3` | `md3`(Material You)/`miui` | | **通知** | `notify.center_enabled` | bool | true | 下拉通知中心开关 | @@ -59,6 +60,8 @@ | | `network.bluetooth_enabled` | bool | false | 蓝牙开关(写 `hciconfig hci0 up/down`;默认关=少耗电) | | **系统** | `system.hostname` | string | `meowos` | 主机名(RFC1035 首标签;bootmenu 的 S89meow_svcd 开机读它 `sethostname`,缺省 `meowos`;供自研 mDNS 应答器 `meow_mdns` 公布 `.local`)[源:M2.54] | +> **字体尺寸标记(M2.5x,随「加载大字库」一起落地)**:字体/主题选择器**每个字体选项标注字库大小标记**,如 `default(common≈4k)` / `gbk≈20k` / `full`,直观标注码位/体积量级,让用户知道所选字库大小。对应 `MeowOS/fonts//font.json` 的 `charset`(`common|gbk|full`)+ `glyph_count`;档位枚举 `MEOW_FONT_TIER_COMMON`(默认/≈4k)/`GBK`(大/≈20k)/`FULL`(全/可选);`gen_font.py --charset common|gbk|full` 按档生成。定案见 MEOWOS_SPEC_REF §5.2「字体档位/大字库」与 §5.7.8 [源:用户 2026-08-30]。 + > 新增设置项时:在清单加一行 + 存储 schema + API 常量/枚举 + 实体 UI 控件 + web 面板控件(四端同步)。 ## 2. 统一存储(单一数据源) diff --git a/docs/MEOWOS_SPEC_REF.md b/docs/MEOWOS_SPEC_REF.md index bb73160..ac932c8 100644 --- a/docs/MEOWOS_SPEC_REF.md +++ b/docs/MEOWOS_SPEC_REF.md @@ -96,6 +96,7 @@ - 控件:`meow_list_source = int (*)(int idx,char *label,int len,void *ctx)`([D2] 定案)`[meow_ui.h]` - 刷新率:`meow_ui_set_refresh_rate(60/30/15)`/`meow_ui_set_refresh_always_max(1)`/`meow_ui_effective_rate()`/`meow_ui_frames_presented()` `[meow_ui.h §7.1]` - 状态栏:`meow_ui_set_statusbar(1/0)`/`meow_ui_statusbar_height()`(非全屏=28,全屏=0)/`meow_ui_set_fullscreen(on)`;状态栏高 **MUI_SBAR_H=28**(用户定 40→28,字号随之为 20)`[MIGRATION_AC §11.1][meow_ui.h]` +- 全屏/状态栏收合(M2.5x):launcher 启动 app/切 activity 据 `app.json fullscreen`/`route..fullscreen` 预先设 SDK 全屏(env **`MEOW_FULLSCREEN`** 注入);进入全屏 = 状态栏**向上收出屏顶、只留一条提示线**(新增 `meow_ui_statusbar_collapsed()` 语义,区分「**收合=状态栏缩为提示线**」vs「**隐藏=高度0**」),带自然过渡动画;全屏下**保留左缘返回手势 + 顶部提示线,顶部下滑仍开通知中心(顶部热区缩小)** `[源:用户 2026-08-30]` - 日志:`MEOW_LOG_DEBUG=0/INFO/WARN/ERROR`;`meow_log(lvl,fmt,...)`/`meow_log_set_level`/`meow_log_get_level` `[meow_ui.h §9]` - 通知:见 §4。 @@ -144,6 +145,7 @@ - **左缘返回手势**:手指从**屏幕左缘 ≤24px** 向内(右)划 **≥90px**,**抬手(release 边沿)才触发** `MEOW_SWIPE_LEFT`("左滑=返回"契约);过程以 `on_swipe_progress` 实时上报 + SDK 自动画顶部进度条 `meow_ui_swipe_hint(x)`(5px 高,top,theme accent 色);x<=0 清条。**中部左/右滑不是返回**(中部右滑→SWIPE_RIGHT,中部左滑→落 TAP)`[MEOW_APP_SDK §6][GESTURE_BACK §2.2/§3]` - 偏差:原「左缘+向左 mdx<-90」物理不可达 → 取「左缘向内(右)划 ≥90px」,事件仍报 `MEOW_SWIPE_LEFT` `[GESTURE_BACK §3]` - **全局状态栏**:SDK 内置,默认显示(时钟/电量/WiFi/蓝牙/音量),1s 轻刷,z 最顶;仅 APP 声明全屏/`fullscreen:1` 才隐藏;非全屏 APP 须用 `meow_ui_statusbar_height()` 做 inset `[meow_ui.h §3.1][MIGRATION_AC §3]` +- **全屏机制(M2.5x,`app.json fullscreen` 即 app 级 + `route..fullscreen` 即 activity 级)**:launcher 启动 app/切 activity 时据声明**预先设 SDK 全屏**(env `MEOW_FULLSCREEN`)。进入/退出全屏:**状态栏向上收出屏顶、只留一条提示线高度**(非全隐藏),带上收/恢复的**自然过渡动画**。**全屏下保留**:左缘返回手势 + 顶部动态伸缩提示线;顶部下滑仍可展开通知中心,但**顶部热区缩小**(不影响全屏 app)`[源:用户 2026-08-30]`(细化既有「全屏=隐藏状态栏/隐藏返回键」的表述,见 §7 A-13) - 沉浸:`app.json immersive:1` → launcher 写 `/tmp/meowapp_state=1` → 隐藏虚拟返回键,手势归 SDK `[APPS §3.5][GESTURE_BACK §2.4]` ### 5.2 主题 @@ -152,17 +154,24 @@ - 主界面背景:深色用 background(可选色)、浅色用 surface(白) `[DEV_NOTES #13]` - **TF 卡多字体库(M2.5x)**: - 目录 **`<卡根>/MeowOS/fonts//`**(每套一个子目录),内含三件套:`font.png`(预渲染灰度 PNG,bit depth 4|8)+ `font.idx`(MFNI v2 索引),二者均由 `font/gen/gen_font.py` 生成;`font.json` 为元数据+完整性校验。**缺任一件 → 该套不进列表 + 日志**。 - - **font.json schema(定版)**:`family`(同目录名,信息用)/`name`(下拉显示名,缺则回退目录名)/`glyph_count`(信息用)/`size_list`(信息用)/`bit_depth`(4|8,信息用)/`sha256_png`(font.png 的 sha256 低 64hex)/`sha256_idx`(font.idx 的 sha256)/`source`(可选)/`license`(可选)。校验失败**仅 WARN 不硬失败**(该套仍可选);缺 font.json 或解析不出 `name` → 跳过。 + - **font.json schema(定版)**:`family`(同目录名,信息用)/`name`(下拉显示名,缺则回退目录名)/`glyph_count`(信息用)/`charset`(`common|gbk|full`,字库档位,信息用+驱动按需加载,见「字体档位/大字库」)/`size_list`(信息用)/`bit_depth`(4|8,信息用)/`sha256_png`(font.png 的 sha256 低 64hex)/`sha256_idx`(font.idx 的 sha256)/`source`(可选)/`license`(可选)。校验失败**仅 WARN 不硬失败**(该套仍可选);缺 font.json 或解析不出 `name` → 跳过。 - **加载优先级**:`font.file`(设置键,默认 `MeowOS/fonts/default/font.png`)指向的字体**存在且能 load → 用它**;否则回退卡默认 `MeowOS/fonts/default/font.png` → 固件 `/usr/share/meowos/font/font.png`。相对路径按卡根 `/usr/data/mnt/sd_0/` 解析。全失败 → 字库不可用,`ui_draw` 回退 5x7 点阵(系统不死)。 - **默认字库目录**:默认字体统一放 **`<卡根>/MeowOS/fonts/default/`**(`font.png`+`font.idx`,可选 `font.json`),不再用 `MeowAPPs/fonts/font.png`;设备旧 `MeowAPPs/fonts/` **保留**(勿删,避免破坏老约定/已有文件),仅新引用一律指向 `MeowOS/fonts/default`。 - **`font.file` 变更即 unload+reload 生效**(设置页回调);拔卡 → 回退固件内置。 - **preset 动态化**:启动/进设置页扫描一次 `MeowOS/fonts/*/`,重建「字体文件」下拉(默认字体 + ≤16 套卡内字体;上限防爆)。扫描仅此二时机,勿放热路径。 - **MeowPanel 预留**:`meow_font_import_zip()`/`meow_font_export_zip()` 为 TODO 声明(不实现),供 MeowPanel 后续接入(`.zip` 导入校验→解压到 `MeowOS/fonts//`、导出打包)。 - **多字体(M2.5x,`display.multi_font`)**:按字符类别(CJK/CJK_SYM/NUM/LATIN/LATIN_SYM/OTHER)逐字符路由,每类可各选一套字库(`font.class_*`,默认空=跟随全局 `font.file`);同 family 去重、同时装载不同字体 ≤4 套(超出 WARN「过多不同字体会增内存」并截断);开关关 = 单字体(现状)。详见 §5.7.7「多字体」。 + - **字体档位 / 大字库(M2.5x)**:抽象出**字库大小档位**,让系统/应用/设置页都知道选的是多大字库 `[源:用户 2026-08-30]`。 + - 档位枚举:`MEOW_FONT_TIER_COMMON`(**默认 / ≈4k** 码位)/ `MEOW_FONT_TIER_GBK`(**大 / ≈20k+**)/ `MEOW_FONT_TIER_FULL`(**全 / 可选**,码位/体积最大)。 + - `font/gen/gen_font.py` 增 `--charset common|gbk|full` 按档生成 `font.png/font.idx`(现行仅 `--extra` 思路升级为**档位选择**,**本次新增**);每个 `MeowOS/fonts//font.json` 标 `charset` + `glyph_count`(覆盖声明/信息用)。 + - **默认用小字库(`common`≈4k)**;仅当 app 声明需大字库(`app.json "fonts":"full"` 或档位)才为该 app 加载大字库(`gbk`≈20k+),见 §5.7.8。 + - **系统设置「加载大字库」开关**(`font.load_full`,强制全系统用大字库),见 SYSTEM_SETTINGS §1;**每个字体选项标注字库大小标记**(如 `default(common≈4k)` / `gbk≈20k` / `full`),让用户知道所选字库量级。 + - 判缺字:SDK `meow_font_has_glyph(cp)` 预判当前字库是否含该码位(缺字 → app 回退/提示)。 ### 5.3 通知配额/转储 - **每 App 最多 4 类型;每类型活动 ≤4;媒体单例(每 App 仅 1 条,可多 App)**;系统事件抽象为 App(`app_id=system`) 走同一配额 `[NOTIFICATION_SYSTEM §7][meow_ui.h][DEV_NOTES #11]` - 通知创建 **10s 后自动转储到 TF `MeowOS/notifications/`**(保留最新 10MB ring,不被电源事件清);正常关机先转储再清板载;无卡可运行 `[NOTIFICATION_SYSTEM §8]` +- **通知 TF 转储(定案 #6,M2.5x)**:转储格式 = **appended 日志行**(每条通知一行,CSV 风格:`id,app_id,分类,标题,时间,…`)+ **按总大小滚动截断**(保留最新 **10MB**)。**启动时清板载通知 + 清内存通知表**(开机清残留/内存表);正常关机/重启前**先转储未上卡通知**再清板载(沿用既有规则)`[源:用户 2026-08-30]` ### 5.4 配置版本单点 / 刷新率 / 息屏 - /etc/meowos_version 版本单点(见 §1.1)`[DEV_NOTES #4]` @@ -175,7 +184,7 @@ - `/` = 程序主体+app.json+资源;`MeowData//{data,cache}` = 数据;首次启动自动建 data/cache 并经 env 注入,exFAT 无软链 `[APPS 附录 §1/§2]` - `.mpk` = tar.gz(app.json + meow 二进制 + 资源),**不含 data/cache**;导入校验 app.json/哈希→解压→数据 init→登入列表 `[APPS 附录 §3]` - 包管理 **CLI `nyai`**(静态 MIPS,部署 `MeowOS/opt/bin/nyai`):install/remove/list/info/search/index/export/upgrade/scan;源 `MeowOS/opt/nyai/sources.list`;索引 `Packages`(Package/Version/Filename/Size/SHA256/Depends/Description)`[APPS 附录 §5]` -- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + **`package`(反域名权威 app_id;==目录名;缺省回退目录名)** + 扩展 `framework`/`immersive`/`icon`/`route.`/`awake.`(keep_awake)/`service.`/`fullscreen`(**未解析**,见矛盾 §A-12) `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2][源:审计 2026-08-30]` +- 应用数上限 **8**(单屏 8 行);扫描深度 1 级;`app.json` 字段:name(仅 ASCII)/exec(静态 MIPS32r2 ELF)/version/author + **`package`(反域名权威 app_id;==目录名;缺省回退目录名)** + 扩展 `framework`/`immersive`/`icon`/`route.`/`awake.`(keep_awake)/`service.`/`fullscreen`(app 级;`route..fullscreen`=activity 级;**本次实现**:launcher 启动/切 activity 据声明预设 SDK 全屏,见 §5.1/§3.1/§A-12)/`fonts`(字体档位 `"full"|"gbk"|"common"`,需大字库时声明,见 §5.7.8) `[APPS §2][MEOW_APP_SDK §9][FRAMEWORK_DESIGN §2][源:审计 2026-08-30][源:用户 2026-08-30]` ### 5.6 冷启动充电屏 - 触发(**三者同时**):冷启动(`poweroff` 写 `/data/meow_cold_boot=1`;`reboot` 写 0)+ 在充电(AC online=1 或 battery status=Charging)+(开机瞬间一次性快照,判定后不回头)`[CHARGING_SCREEN §0]` @@ -287,6 +296,12 @@ - **launcher env 注入**(launch_app,app 跟随系统字体):`MEOW_MULTI_FONT=0/1`;`MEOW_FONT_PNG/IDX`(全局,保留);6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 读 env 构建同一映射;无 env/关 → 单字体。 - **持久化**:类别字体设置在 settings.conf(板载,无卡也可存),经 `meow_settings_*` 读写,随设置页变更即重配。 - **内存**(推断解码 atlas = `plane_w×plane_h×(depth 4→0.5 / 8→1.0)` 字节;每套另加 glyph 数组 ≈ glyph_count×16B、idx 头保留约 128KB,idx 原始缓冲载毕即释)。实测仓库字库(depth 4):misans(CJK)≈2.44MiB、source_han_sans(CJK)≈2.53MiB、meslo(Latin)≈1.68MiB、google_sans(Latin)≈1.12MiB;按用户估算口径(**depth 8**):CJK ≈ 5-6MB、Latin ≈ 0.3-0.5MB(后者为窄覆盖 Latin 专库)。**典型「1CJK+1Latin」增量**:全局=CJK,另加一套 Latin → 增加 ≈0.3-1.7MB(depth 8 约 0.5MB;实测 depth4 latin 1.1-1.7MB);全局=Latin 另加一套 CJK → 增加 ≈2.4-2.5MB(depth 8 约 5-6MB)。`display.multi_font=0` 时仅全局一套,无任何额外装载。 +- **5.7.8 字体档位分层 / 按需大字库(app 声明 + 系统开关)** `[源:用户 2026-08-30]` + - **口径**:默认用**小字库 `common`(≈4k 码位)**;当 app 声明需要**大字库**(如小说阅读器,`app.json` 声明 `"fonts":"full"` 或档位 `"gbk"`/`"common"`)时,才为该 app 加载**大字库 `gbk`(≈20k+)**。档位与 `gen_font.py --charset`、`font.json charset/glyph_count` 见 §5.2「字体档位/大字库」。 + - **app 级 / activity 级**:app 级以 `app.json "fonts":""` 声明;activity 级(如需)以 `route..fonts` 声明(沿用 `route.` 扩展口径)。launcher 启动 app/切 activity 时据声明选加载大/小字库,并经 env 注入(`MEOW_FONT_PNG/IDX` 指向所选档位字库;新增 `MEOW_FONT_TIER` 标注档位)。 + - **系统设置「加载大字库」**(`font.load_full`,默认 0):开 → **强制全系统用大字库(gbk 档)**,覆盖 app 未声明 + 覆盖默认 `font.file`;关(默认)→ 默认小字库,app 声明才大字库。 + - **SDK 判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位 → app 据此决定回退/提示(避免 `common` 小字库下静默豆腐或缺字)。 + - **⚠️ 与 §5.7.3 冲突**:§5.7.3 `"fonts":[…]` 为**应用自带字库句柄数组**;本小节 `"fonts":"full"` 为**字库档位字符串**。同名键两种语义,需统一解析口径(见 §7 A-14)。 --- @@ -310,5 +325,7 @@ - **A-9 SDK 通知转储**:NOTIFICATION_SYSTEM §8「创建 10s 后自动转储到 TF」;meow_ui.h 现仅「活动表+meow_log 打日志,真实投递 UI/D-Bus 为后续」。→ 10s 转储/TF 尚未实现,属"已定未做"。 - **A-10 版本来源细解**:DEV_NOTES #4「/etc/meowos_version 唯一来源;MEOWOS_VERSION 宏仅 dev 占位永不动」;APPS.md §4.6「MEOWOS_VERSION 宏=单一来源,build 时 sed 写入 etc/meowos_version」。→ 两者都强调"写入同源",但"谁是源头"措辞矛盾;实践上 build 脚本 echo 生成 /etc/meowos_version,宏作屏显 dev 占位。 - **A-11 `MeowOS/opt/` vs `MeowOpt/`**:§1.2 树中 `MeowOS/opt/{bin,nyai,sources.list},lib/,tools/`(nyai 包管 + 工具)与新增顶层 `MeowOpt/`(板载外部工具/二进制,如 mdnsd)并存,命名仅大小写之差、语义可能重叠。→ **两者是否为同一用途/是否需归并待确认**;当前按各自文档记录,勿混用。 -- **A-12 `fullscreen` 字段未解析**:§5.5 与 MEOW_APP_SDK §9 将 `fullscreen` 列为 app.json 字段,但 `meow_parse_app_json()` **并未读取该键**(实现只解析 name/exec/icon/package/framework/immersive/route./awake./service.)。→ 文档与实现不一致,属"已定未做";若要用需在解析器补读并处理。 +- **A-12 `fullscreen` 字段(已定 → 本次实现)**:§5.5 与 MEOW_APP_SDK §9 将 `fullscreen` 列为 app.json 字段,但 `meow_parse_app_json()` **此前并未读取该键**(实现只解析 name/exec/icon/package/framework/immersive/route./awake./service.),属"已定未做"。→ **本次实现**:launcher 启动 app/切 activity 据 `app.json fullscreen`/`route..fullscreen` 预设 SDK 全屏(env `MEOW_FULLSCREEN`),见 §5.1/§3.1。注意全屏语义由「隐藏状态栏+隐藏返回键」细化为「状态栏收合至提示线 + 保留左缘返回+顶部提示线+下滑通知中心(热区缩小)」(见 A-13)。 +- **A-13 全屏语义细化(状态栏「隐藏」→「收合至提示线」)**:既有 §3.1/§5.1 描述全屏=隐藏状态栏(`meow_ui_statusbar_height()`=0、隐藏返回键=沉浸);本次定案细化为**进入/退出全屏:状态栏向上收出屏顶、只留一条提示线高度**,**全屏下保留**左缘返回手势 + 顶部动态伸缩提示线,且**顶部下滑仍可开通知中心(顶部热区缩小)**。→ 需在 SDK 端明确「收合(collapsed)」状态(新增 `meow_ui_statusbar_collapsed()` 等)与「隐藏」的差异,UI 依此绘制提示线。 +- **A-14 `fonts` 键二义**:§5.7.3 `"fonts":[…]`=应用自带字库句柄数组;§5.2/§5.7.8 `"fonts":"full"|"gbk"|"common"`=字库档位字符串。→ 同名键两种语义,解析器需区分(字符串→档位 / 数组→句柄);若避免歧义可改用专键如 `"font_tier"`(**未拍板**)。 `[各源标注见上]` diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 38b9995..5edf8f0 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/meow_launcherd.c b/patch_v2/meow_launcherd/meow_launcherd.c index b3f16c7..36482e9 100644 --- a/patch_v2/meow_launcherd/meow_launcherd.c +++ b/patch_v2/meow_launcherd/meow_launcherd.c @@ -915,6 +915,20 @@ static int meow_font_effective_paths(char *out_png, int plen, char *out_idx, int return -1; } +/* 【tier 选择入口,font 域占位】按字体档位取「当前生效字体」png/idx,供 launch_app 注入 + * MEOW_FONT_PNG/IDX/APP(配合 MEOW_FONT_TIER)。基座只实现 tier=COMMON(=meow_font_effective_paths); + * tier>0 的大字库(GBK/Full)绝对路径由后续 font 代理据此演进填充(约定 MeowOS/fonts// + * /…,或 settings 键 font.tier_gbk / font.tier_full —— 见 MEOW_APP_SDK.md 字体分层)。 + * 未实现前暂回退全局字体。返回 0 成功,-1 无可用。 */ +static int meow_font_effective_paths_tiered(int tier, char *out_png, int plen, + char *out_idx, int ilen, char *out_family, int flen) +{ + if (tier == MEOW_FONT_TIER_COMMON || tier < MEOW_FONT_TIER_COMMON) + return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen); + /* TODO(font 代理):按 tier 解析大字库绝对路径(GBK/Full);暂回退全局(tier0)。 */ + return meow_font_effective_paths(out_png, plen, out_idx, ilen, out_family, flen); +} + /* M2.5x 多字体:从 settings 读 display.multi_font + 6 类 font.class_*,把各自相对路径按卡根 * 解析为绝对路径,去重装载到 meow_font 槽位(同 family 去重 & ≤4 套上限由 * meow_font_config_multifont 统一处理)。仅当全局字库已装载(mfont_ready / slot0)时生效; @@ -1308,6 +1322,7 @@ struct meow_route { char act[16]; /* route 键后缀,如 "nowplaying" */ char chain[MEOW_CHAIN_MAX]; /* 如 "home;search;nowplaying" */ int keep_awake; /* M2.17:该 activity 常亮(app.json awake.=1 / route..keep_awake=1) */ + int fullscreen; /* 活动级全屏声名(app.json route..fullscreen=1;M3 活动切换时消费) */ }; /* 服务行:service.=mode:restart:max:window_ms(M1 仅解析存储,M4 保活) */ @@ -1328,6 +1343,8 @@ struct app { /* ---- 阶段 D · M1:app.json 路由/框架/服务/图标(scan_apps 解析)---- */ char framework; /* '1'=framework=true(看门狗返回发 SIGUSR1) */ char immersive; /* '1'=immersive=true(隐藏虚拟返回键,返回手势归 SDK) */ + char fullscreen; /* '1'=app.json fullscreen=true(注入 MEOW_FULLSCREEN=1,SDK 预先设全屏) */ + char fonts; /* 字库档:0=默认4K常用 1=大字库GBK 2=Full(app.json "fonts";注入 MEOW_FONT_TIER) */ char icon[129]; /* icon= 路径 */ int nroute; /* route. 行数 */ struct meow_route routes[MEOW_MAX_ROUTES]; @@ -1805,6 +1822,8 @@ static void meow_parse_app_json(FILE *f, struct app *a) char name[17] = "", exec[129] = "", icon[129] = ""; char package[MEOW_APP_ID_MAX] = ""; int fram = 0, immersive = 0; + int fullscreen = 0; /* app.json fullscreen=(注入 MEOW_FULLSCREEN env,SDK 预先设全屏) */ + int fonts = 0, fonts_set = 0; /* app.json "fonts":<档>(0=默认4K常用 1=大字库GBK 2=Full;缺省 0) */ struct app tmp; memset(&tmp, 0, sizeof tmp); while (fgets(buf, sizeof buf, f)) { @@ -1816,8 +1835,8 @@ static void meow_parse_app_json(FILE *f, struct app *a) char *rp = strstr(buf, "route."); if (rp && tmp.nroute < MEOW_MAX_ROUTES) { rp += 6; /* 跳过 "route." */ - int idl = 0; char id[16] = ""; - while (*rp && *rp != '"' && *rp != ':' && *rp != '=' && idl < 15) + int idl = 0; char id[32] = ""; /* 读满整键(识别 ".fullscreen" 后缀,cap 31) */ + while (*rp && *rp != '"' && *rp != ':' && *rp != '=' && idl < 31) id[idl++] = *rp++; id[idl] = 0; if (idl > 0) { @@ -1826,16 +1845,30 @@ static void meow_parse_app_json(FILE *f, struct app *a) if (*vp == ':' || *vp == '=') vp++; while (*vp == ' ' || *vp == '\t') vp++; if (*vp == '"') vp++; - int vl = 0; char chain[MEOW_CHAIN_MAX] = ""; - while (*vp && *vp != '"' && *vp != ',' && *vp != '\n' && - *vp != '\r' && vl < MEOW_CHAIN_MAX - 1) - chain[vl++] = *vp++; - chain[vl] = 0; - while (vl > 0 && (chain[vl - 1] == ' ' || chain[vl - 1] == '\t')) - chain[--vl] = 0; - strncpy(tmp.routes[tmp.nroute].act, id, 15); - strncpy(tmp.routes[tmp.nroute].chain, chain, MEOW_CHAIN_MAX - 1); - tmp.nroute++; + /* route..fullscreen=:活动级全屏声明(M1 顺带;M3 活动切换时消费)。 + * 命中既有 route. 置 fullscreen;不新建 route(避免 id 被截断误建 "…fulls")。 */ + if (idl >= 11 && !strcmp(id + idl - 11, ".fullscreen")) { + id[idl - 11] = 0; /* 去掉 ".fullscreen" 还原 act */ + char v[8] = ""; int k = 0; + while (*vp && *vp != '"' && *vp != ',' && *vp != '\n' && + *vp != '\r' && k < 7) v[k++] = *vp++; + v[k] = 0; + int on = (!strcmp(v, "1") || !strcmp(v, "true") || !strcmp(v, "yes")); + for (int i = 0; i < tmp.nroute; i++) + if (!strcmp(tmp.routes[i].act, id)) { tmp.routes[i].fullscreen = on; break; } + } else { + int vl = 0; char chain[MEOW_CHAIN_MAX] = ""; + while (*vp && *vp != '"' && *vp != ',' && *vp != '\n' && + *vp != '\r' && vl < MEOW_CHAIN_MAX - 1) + chain[vl++] = *vp++; + chain[vl] = 0; + while (vl > 0 && (chain[vl - 1] == ' ' || chain[vl - 1] == '\t')) + chain[--vl] = 0; + strncpy(tmp.routes[tmp.nroute].act, id, 15); + tmp.routes[tmp.nroute].act[15] = 0; /* 显式收尾(原依赖 id 读长 ≤15) */ + strncpy(tmp.routes[tmp.nroute].chain, chain, MEOW_CHAIN_MAX - 1); + tmp.nroute++; + } } } /* awake.=1(M2.17 keep_awake:该 activity 常亮,超时不自动息屏)。 @@ -1881,6 +1914,29 @@ static void meow_parse_app_json(FILE *f, struct app *a) immersive = 1; } } + /* fullscreen=true(app 级:注入 MEOW_FULLSCREEN=1,SDK 端 meow_ui_init 预先设全屏)。 + * 与 immersive 是两回事:fullscreen 隐藏状态栏/沉浸,immersive 关虚拟返回键。 */ + if (!fullscreen && strstr(buf, "fullscreen")) { + char v[8] = ""; + if (json_str(buf, "fullscreen", v, sizeof v) > 0) { + if (!strcmp(v, "true") || !strcmp(v, "1") || !strcmp(v, "yes")) + fullscreen = 1; + } + } + /* "fonts":<档>(字库档:0=默认4K常用 1=大字库GBK 2=Full;可选,缺省 0)。 + * launcher 注入 MEOW_FONT_TIER;大字库装载由后续 font 代理实现。 */ + if (!fonts_set && strstr(buf, "fonts")) { + char v[8] = ""; + if (json_str(buf, "fonts", v, sizeof v) > 0) { + if (!strcmp(v, "common") || !strcmp(v, "0")) fonts = 0; + else if (!strcmp(v, "gbk") || !strcmp(v, "1")) fonts = 1; + else if (!strcmp(v, "full") || !strcmp(v, "2")) fonts = 2; + else fonts = atoi(v); /* 数字兜底(越界收敛到 [0,2]) */ + if (fonts < 0) fonts = 0; + if (fonts > 2) fonts = 2; + fonts_set = 1; + } + } /* service.=mode:restart:max:window_ms(M1 解析存储,保活 M4) */ char *sp = strstr(buf, "service."); if (sp && tmp.nsvc < MEOW_MAX_SVCS) { @@ -1911,6 +1967,8 @@ static void meow_parse_app_json(FILE *f, struct app *a) strncpy(a->package, package, sizeof a->package - 1); a->framework = fram ? '1' : 0; a->immersive = immersive ? '1' : 0; + a->fullscreen = fullscreen ? '1' : 0; /* 注入 MEOW_FULLSCREEN env */ + a->fonts = (char)(fonts < 0 ? 0 : (fonts > 2 ? 2 : fonts)); /* 注入 MEOW_FONT_TIER env */ strncpy(a->icon, icon, 128); a->nroute = tmp.nroute; if (tmp.nroute) memcpy(a->routes, tmp.routes, sizeof(a->routes[0]) * (size_t)tmp.nroute); @@ -2263,6 +2321,19 @@ static void settings_sync_cache_listener(const char *key) else if (!strcmp(key, "power.forceoff_ms")) { int v; if (meow_settings_get_int("power.forceoff_ms", &v) == 0) power_forceoff_ms = v; } } +/* ---- 通知 dump 回调占位(通知#6 转储 TF 卡;接线/占位,不做 TF 写)---- + * meow_notif 在 TTL 到点(meow_notif_tick,§8 10s)调用此回调归档一条通知,随后释放槽。 + * 基座只把回调挂上来(见 settings_load 的 meow_notif_set_dump_cb);真实 TF 落盘 + * (按 NOTIFICATION_SYSTEM.md §8 / TF_CARD_LAYOUT.md 写 <卡>/MeowOS/notify/...)由后续 + * 通知代理在回调体内实现。注意 meow_notif.c 的 archive_one 自身已 log 一行 "dump: ...", + * 故本回调保持轻量(避免重复刷屏),落盘逻辑填在下方 TODO 处。 */ +static void notif_dump_tf_placeholder(const meow_notif_t *n) +{ + (void)n; + /* TODO(通知代理, M2c):在此写 TF 卡 /notify//.json 等; + * 基座不实现,仅保留挂接点(触发时机=meow_notif_tick 内 10s 到点/止机转储)。 */ +} + /* 启动加载(M2.11/M2.24):统一由 meow_settings 后端读 settings.conf; * 此处仅注册监听(一次性)+ 把 backend 当前值同步到缓存全局。重载(应用期 * watchdog 可能改音量)等价于 meow_settings_init() + 手动同步。 */ @@ -2271,6 +2342,7 @@ static void settings_load(void) { meow_settings_init(); /* 幂等:注册默认 + 读 settings.conf */ meow_notif_init(); /* M2a:通知存储初始化(幂等,读 notify.* 配额) */ + meow_notif_set_dump_cb(notif_dump_tf_placeholder); /* 基座(通知#6):挂 dump 回调(TF 转储占位,M2c 落盘由后续代理实现) */ if (!settings_cache_registered) { meow_settings_add_listener(settings_sync_cache_listener); settings_cache_registered = 1; @@ -6732,8 +6804,10 @@ static int launch_app(struct app *a) char env_app[MEOW_APP_ID_MAX + 16], env_lvl[48]; char env_fpng[400], env_fidx[400], env_fapp[96]; char env_mf[24]; + char env_tier[24]; + char env_fs[20]; char env_cls[MEOW_FCLS_COUNT][2][400]; - char *envp[32]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),class(≤12),data,cache */ + char *envp[40]; /* PATH,HOME,log_dir,log_app,log_lvl,font 全局(3),多字体(1),tier(1),class(≤12),fullscreen(1),data,cache */ int ne = 0; const char *cp = getenv("PATH"); snprintf(env_path, sizeof env_path, "PATH=%s", cp ? cp : "/usr/bin:/bin"); @@ -6772,8 +6846,13 @@ static int launch_app(struct app *a) meow_settings_get_bool("display.multi_font", &mf_on); snprintf(env_mf, sizeof env_mf, "MEOW_MULTI_FONT=%d", mf_on ? 1 : 0); envp[ne++] = env_mf; - have_global = (meow_font_effective_paths(fpng, sizeof fpng, fidx, sizeof fidx, - fam, sizeof fam) == 0); + /* M2.x(基座):字库档 MEOW_FONT_TIER=<0|1|2> —— 按 app.json "fonts" 声明注入; + * 0=默认4K常用(未声明/缺省),1=大字库GBK,2=Full。SDK meow_ui_gfx 读入存档, + * 真正按档装载大字库由后续 font 代理实现(见 meow_font_effective_paths_tiered)。 */ + snprintf(env_tier, sizeof env_tier, "MEOW_FONT_TIER=%d", a->fonts); + envp[ne++] = env_tier; + have_global = (meow_font_effective_paths_tiered(a->fonts, fpng, sizeof fpng, + fidx, sizeof fidx, fam, sizeof fam) == 0); if (have_global) { snprintf(env_fpng, sizeof env_fpng, "MEOW_FONT_PNG=%s", fpng); snprintf(env_fidx, sizeof env_fidx, "MEOW_FONT_IDX=%s", fidx); @@ -6801,6 +6880,14 @@ static int launch_app(struct app *a) envp[ne++] = env_cls[c][1]; } } + /* 全屏声明:app.json "fullscreen"(app 级)→ 注入 MEOW_FULLSCREEN=1,SDK 端 + * meow_ui_init 据此预先设全屏。基座只接 app 级;活动级 route..fullscreen + * (meow_route.fullscreen)在 M3 活动切换时由后续代理消费(此处暂不下发)。 + * 未声明则不注入(SDK 默认非全屏)。 */ + if (a->fullscreen) { + snprintf(env_fs, sizeof env_fs, "MEOW_FULLSCREEN=1"); + envp[ne++] = env_fs; + } char pkg[64]; if (meow_pkg_name(a->exec, pkg, sizeof pkg) == 0) { snprintf(env_data, sizeof env_data, "MEOW_DATA_DIR=/usr/data/mnt/sd_0/MeowData/%s/data", pkg); diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index bca5b25..bc17e86 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -175,6 +175,24 @@ void meow_ui_set_fullscreen(int on); * 全屏/隐藏时返回 0。 */ int meow_ui_statusbar_height(void); +/* ---- 状态栏「上收出屏只留提示线」(collapsed,草案/待定案——本批仅加声明)---- + * 供全屏调用:把状态栏内容上移出屏顶、只留一条「提示线」高度(可下拉还原)。 + * 与 meow_ui_set_fullscreen(1) 的关系:fullscreen 隐藏整条状态栏(沉浸,inset=0); + * collapsed 仍视状态栏为「激活」,左缘返回手势(SWIPE_LEFT)与顶部伸缩提示线 + * (meow_ui_swipe_hint)依旧生效,只是其内容上收、仅留提示线——用于「不完全沉浸」, + * 保留一处可下拉拉出状态栏的入口。顶部下滑热区 meow_ui_statusbar_hotzone() 在全屏时缩小。 + * 实现位置:meow_ui_gfx.c(基座让 on 生效:true→只画提示线/内容上移);逐帧过渡动画 + * (上收/恢复插值)为待定案,后续代理可经 meow_ui_statusbar_animate(ms) 补齐。 */ +void meow_ui_statusbar_set_collapsed(int on); /* on=1 上收(内容上移出屏、只留提示线);on=0 恢复 */ +int meow_ui_statusbar_collapsed(void); /* 当前是否处于「上收」目标(1=是) */ +void meow_ui_statusbar_animate(int ms); /* TBD:上收/恢复过渡时长(ms);占位,后续代理实现插值 */ + +/* 顶部下滑热区(下拉展开状态栏/通知)高度(px)。 + * 非全屏=整条状态栏可视高度;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 + * 返回「可用于起始下拉」的热区高度;由输入层/后续代理据此判定下拉起点。 */ +int meow_ui_statusbar_hotzone(void); +#define MEOW_SBAR_HOTZONE_FS 4 /* 全屏时缩小后的顶部热区高度(px,草案值) */ + /* ============================================================ */ /* §7 动画/时间(B1 最小可用) */ /* ============================================================ */ @@ -309,6 +327,27 @@ int meow_notify_has_active(void); /* 1=尚有活动通知,0=无 * const char *meow_notify_urgency_str(meow_notify_urgency u); const char *meow_notify_category_str(meow_notify_category cat); +/* ============================================================ */ +/* §10 字体分层 / 大字库(草案/待定案 —— 本批仅加声明/枚举/宏,不实现) */ +/* ============================================================ */ +/* 字体「档位」概念:launcher 按 app.json 声明的字库档("fonts":<档>)向 app 注入 + * MEOW_FONT_TIER=<0|1|2>;SDK(meow_ui_gfx / 后续 meow_font 大字库加载)据档选库: + * MEOW_FONT_TIER_COMMON=0 默认 4K 常用字库; + * MEOW_FONT_TIER_GBK =1 大字库 GBK; + * MEOW_FONT_TIER_FULL =2 Full(全字符集)。 + * 后续 font 域代理据此在 meow_font.c / gen_font.py 实现大字库生成与按档装载。 + * 既有 meow_font_* 符号(class_of/has_glyph/load_slot/config_multifont…)保留不动; + * 大字库装载以 set_tier/tier 作为入口,或由后续代理按 meow_font_config_multifont 扩展。 */ +#define MEOW_FONT_TIER_COMMON 0 +#define MEOW_FONT_TIER_GBK 1 +#define MEOW_FONT_TIER_FULL 2 + +/* 设当前字体档(0/1/2,越界 clamp 到合法值),返回生效档。 + * 基座在 meow_ui_gfx.c 定义(只存档 + 触发重载请求);真正按档装载大字库由后续 font 代理实现。 */ +int meow_font_set_tier(int tier); +/* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */ +int meow_font_tier(void); + #ifdef __cplusplus } #endif diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index 83a6abe..c429599 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -63,6 +63,24 @@ static int g_dirty_full = 1; static char *g_dump_path = NULL; +/* ---- 状态栏「上收出屏只留提示线」(collapsed,基座)---- + * g_sbar_collapsed = 目标态(1=上收);g_sbar_collapse_h = 当前实际上收偏移 + * (0..MUI_SBAR_H:0=完全展开,MUI_SBAR_H=内容全部上移出屏、仅留提示线)。 + * 基座让 meow_ui_statusbar_set_collapsed(on) 立即生效(true→只画提示线/内容上移); + * 过渡(每帧插值)留待后续代理或经 meow_ui_statusbar_animate(ms) 启用的简易线性逼近。 */ +#define MUI_SBAR_HINT_H 6 /* 上收状态下顶部提示线高(px) */ +static int g_sbar_collapsed = 0; /* 目标:1=上收 */ +static int g_sbar_collapse_h = 0; /* 当前上收偏移(0..MUI_SBAR_H) */ +static int g_sbar_collapse_anim_ms = 0; /* 过渡时长(0=瞬切);meow_ui_statusbar_animate 设置 */ +static int g_sbar_collapse_from_h = 0; /* 本次过渡起点偏移 */ +static unsigned long long g_sbar_collapse_start_ms = 0; /* 本次过渡起始(monotonic ms) */ + +/* ---- 字体档位(MEOW_FONT_TIER,基座存档 + 公共 get/set)---- + * launcher 按 app.json "fonts":<档> 注入 MEOW_FONT_TIER=<0|1|2>;SDK 读入并以 + * meow_font_set_tier/tier 暴露。真正按档装载大字库(GBK/Full)由后续 font 代理在 + * meow_font.c / meow_gfx_load_multifont 挂钩实现——此处仅记录档位不换库。 */ +static int g_font_tier = MEOW_FONT_TIER_COMMON; + /* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)---- * meow_ui_flush()/present() 的 fps 档位与自适应降档 governor。launcher 不再自带 * 一套帧率门控;launcher 与 app 统一走这里 → 全局观感/刷新率一致。 @@ -499,6 +517,71 @@ static void dump_ppm(void) fclose(f); } +/* ================= 状态栏「上收出屏只留提示线」(collapsed,基座) ================= */ +/* 每帧推进上收/恢复:向目标偏移(collapsed→MUI_SBAR_H、否则→0)线性逼近; + * 无过渡时长(g_sbar_collapse_anim_ms<=0)= 一步到位(基座默认)。动画插值骨架在此, + * 后续代理可换缓动/每帧步进。 */ +static void statusbar_collapse_step(void) +{ + int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0; + if (g_sbar_collapse_h == tgt) return; + if (g_sbar_collapse_anim_ms <= 0) { g_sbar_collapse_h = tgt; return; } + unsigned long long now = now_ms(); + unsigned long long el = now - g_sbar_collapse_start_ms; + int span = g_sbar_collapse_anim_ms; + if ((int)el >= span) { g_sbar_collapse_h = tgt; return; } + int delta = tgt - g_sbar_collapse_from_h; + g_sbar_collapse_h = g_sbar_collapse_from_h + + (int)((long long)delta * (long long)el / (long long)span); +} + +/* 上收中是否需要逐帧重画(动画未达目标位置时置位,强制每帧推进)。 */ +static int statusbar_collapse_in_transition(void) +{ + int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0; + return g_sbar_collapse_h != tgt; +} + +/* 上收状态栏:内容整体上移出屏(不画时钟/电量),仅在顶部画一条「提示线」。 + * 与 meow_ui_swipe_hint(左滑返回进度条,5px,动态)不同:此线是「状态栏可下拉还原」的 + * 常驻提示(主题强调色下边缘)。原状态栏整块清回主题背景,避免残影(app inset 之内)。 */ +static void statusbar_collapsed_render(int h) +{ + (void)h; + const meow_theme *th = meow_theme_get(); + int w = meow_ui_width(); + /* h 越大内容越贴顶;基座:折叠态整块清底 + 顶部提示线(提示线下缘=主题 accent) */ + meow_ui_fill_rect(0, 0, w, MUI_SBAR_H, th->bg); + meow_ui_fill_rect(0, MUI_SBAR_HINT_H - 1, w, 1, th->accent); +} + +/* ---- 公共 API(基座实现;动画插值骨架见 statusbar_collapse_step)---- */ +void meow_ui_statusbar_set_collapsed(int on) +{ + int tgt = on ? 1 : 0; + if (tgt == g_sbar_collapsed) return; + g_sbar_collapsed = tgt; + g_sbar_collapse_from_h = g_sbar_collapse_h; + g_sbar_collapse_start_ms = now_ms(); + dirty_full(); /* 顶栏显隐/上收变化 → 全帧重画 */ +} + +int meow_ui_statusbar_collapsed(void) { return g_sbar_collapsed; } + +void meow_ui_statusbar_animate(int ms) +{ + g_sbar_collapse_anim_ms = (ms > 0) ? ms : 0; /* 0=瞬切(基座默认) */ +} + +int meow_ui_statusbar_hotzone(void) +{ + /* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 + * 上收(collapsed、非全屏)时热区=提示线下缘高度。全屏态由 meow_ui_sys 维护。 */ + if (meow_ui_sys_statusbar_active()) + return g_sbar_collapsed ? MUI_SBAR_HINT_H : MUI_SBAR_H; + return MEOW_SBAR_HOTZONE_FS; +} + /* 顶部状态栏条是否被本次脏区触及(含整帧):触及或数据已变(force)才需要重画顶条。 */ static int statusbar_touched(void) { @@ -519,13 +602,20 @@ static void present(void) if (!g_refresh_always_max) { if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return; } - /* 全局状态栏(A-0):上屏前把状态栏画进 back_buf 顶部 MUI_SBAR_H 行(z 最顶)。 + /* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部 MUI_SBAR_H 行。 * 全屏/隐藏时 active()==0 不画(沉浸),app 内容可占满整屏。触发条件: - * 数据已变(1s 轻刷/显隐切换) 或 顶条被 app 内容触及(含整帧清+绘,未 inset 时被顶条覆盖)。 - * 无需每帧重画——app 局部刷新不触及顶条时,back_buf/screen 顶条保持不变,省一次 blit。 */ + * 数据已变(1s 轻刷/显隐切换) / 顶条被 app 内容触及 / 上收动画未到位(逐帧推进)。 + * 展开态仍走 meow_ui_sys_statusbar_render()(meow_ui_sys.c 不改,留给后续代理); + * 上收折叠态(collapse_h>=MUI_SBAR_H)由基座只画提示线(内容上移出屏)。 */ if (meow_ui_sys_statusbar_active() && - (meow_ui_sys_statusbar_force() || statusbar_touched())) - meow_ui_sys_statusbar_render(); + (meow_ui_sys_statusbar_force() || statusbar_touched() || + statusbar_collapse_in_transition())) { + statusbar_collapse_step(); + if (g_sbar_collapse_h >= MUI_SBAR_H) + statusbar_collapsed_render(g_sbar_collapse_h); + else + meow_ui_sys_statusbar_render(); + } if (g_fb_mem) { ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */ unsigned long long tbg = now_ms(); @@ -575,6 +665,23 @@ unsigned long long meow_ui_frames_presented(void) { return g_frames_presented; } int meow_gfx_is_device(void) { return g_fb_mem != NULL; } int meow_gfx_mfont_ready(void) { return g_mfont_ready; } +/* ---- 字体档位(公共 API,基座定义)---- + * 注意:meow_font_tier()/meow_font_set_tier() 已在 meow_ui_gfx.c 定义(符号唯一), + * 后续 font 代理勿在 meow_font.c 重复定义;可在 meow_font.c 实现「按档装载大字库」并在 + * meow_font_set_tier 的钩子处唤起(见下)。 */ +int meow_font_tier(void) { return g_font_tier; } + +int meow_font_set_tier(int tier) +{ + if (tier < MEOW_FONT_TIER_COMMON) tier = MEOW_FONT_TIER_COMMON; + if (tier > MEOW_FONT_TIER_FULL) tier = MEOW_FONT_TIER_FULL; + if (tier == g_font_tier) return g_font_tier; + g_font_tier = tier; + /* TBD(基座):真正按档装载大字库(meow_font_load_tier 或 config_multifont 扩展)由 + * 后续 font 代理在 meow_font.c 实现;此处仅记录档位 + 标记重载(下一轮 load 读 tier)。 */ + return g_font_tier; +} + /* ================= 初始化/销毁 ================= */ /* 字库加载优先级(M2.x): * 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file) @@ -584,6 +691,16 @@ int meow_gfx_mfont_ready(void) { return g_mfont_ready; } * 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */ static int load_font(void) { + /* 字体档位(MEOW_FONT_TIER env,由 launcher 按 app.json "fonts" 注入): + * 0=默认4K常用 1=大字库GBK 2=Full。基座只读入存档 + 对齐公共 get/set; + * 真正按档装载大字库(GBK/Full 的字库路径/面)由后续 font 代理在 meow_font.c / + * meow_gfx_load_multifont 挂钩实现——此处仍先装载默认(tier0)字库,档位仅记录。 */ + const char *etr = getenv("MEOW_FONT_TIER"); + if (etr && etr[0]) { + g_font_tier = atoi(etr); + if (g_font_tier < MEOW_FONT_TIER_COMMON) g_font_tier = MEOW_FONT_TIER_COMMON; + if (g_font_tier > MEOW_FONT_TIER_FULL) g_font_tier = MEOW_FONT_TIER_FULL; + } const char *ep = getenv("MEOW_FONT_PNG"); const char *ei = getenv("MEOW_FONT_IDX"); if (ep && ep[0] && ei && ei[0] && meow_font_load(ep, ei) == 0) return 1; @@ -677,6 +794,11 @@ int meow_ui_init(const meow_ui_opts *opts) g_init_ok = 1; dirty_full(); meow_ui_sys_init(); /* A-0:系统状态读取器首刷(电量/WiFi/蓝牙/音量),状态栏就绪 */ + /* 全屏预置(基座):launcher 按 app.json "fullscreen" 注入 MEOW_FULLSCREEN=1,SDK 据此 + * 预先设全屏(隐藏整条状态栏/沉浸,statusbar_active()==0、statusbar_height()==0)。 + * 未注入/非"1"=非全屏(默认)。放在 sys_init 后使首帧即为全屏态。 */ + const char *efs = getenv("MEOW_FULLSCREEN"); + if (efs && efs[0] == '1') meow_ui_set_fullscreen(1); return 0; }