From c3f70ed893964f7b77928fc50c0c7ed32e16ce10 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 01:12:34 +0800 Subject: [PATCH 01/49] =?UTF-8?q?MeowOS=20for=20HiBy=20R3=20Pro=20II=20?= =?UTF-8?q?=E2=80=94=20=E5=85=AC=E5=85=B1=E5=8F=91=E5=B8=83=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=88=E5=8E=86=E5=8F=B2=E5=B7=B2=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库 --- build_sdk.sh | 29 + meow_ui.h | 315 ++ meow_ui_gfx.c | 650 ++++ meow_ui_ime.c | 235 ++ meow_ui_img.c | 126 + meow_ui_input.c | 333 ++ meow_ui_internal.h | 65 + meow_ui_log.c | 279 ++ meow_ui_notify.c | 253 ++ meow_ui_sys.c | 231 ++ meow_ui_sys.h | 57 + meow_ui_theme.c | 197 ++ meow_ui_widget.c | 135 + stb_image.c | 13 + stb_image.h | 7988 ++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 10906 insertions(+) create mode 100755 build_sdk.sh create mode 100644 meow_ui.h create mode 100644 meow_ui_gfx.c create mode 100644 meow_ui_ime.c create mode 100644 meow_ui_img.c create mode 100644 meow_ui_input.c create mode 100644 meow_ui_internal.h create mode 100644 meow_ui_log.c create mode 100644 meow_ui_notify.c create mode 100644 meow_ui_sys.c create mode 100644 meow_ui_sys.h create mode 100644 meow_ui_theme.c create mode 100644 meow_ui_widget.c create mode 100644 stb_image.c create mode 100644 stb_image.h diff --git a/build_sdk.sh b/build_sdk.sh new file mode 100755 index 0000000..7d502e4 --- /dev/null +++ b/build_sdk.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# build_sdk.sh — 构建 meow_ui.a(mipsel 静态库,含 meow_font.o 内联打包, +# 应用只需链 meow_ui.a + libz.a + -lpthread)。 +set -e +ROOT="$HOME/Project/R3ProII" +cd "$(dirname "$0")" +CC="$ROOT/.tools/zig-mipsel-cc" +AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架构无关) + +rm -f meow_ui.a *.o + +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys stb_image" +for f in $OBJS; do + echo "== cc $f ==" + "$CC" -Os -static -c "$f.c" -o "$f.o" +done + +# meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库) +TMP="$(mktemp -d)" +( cd "$TMP" && "$AR" x "$ROOT/patch_v2/meow_launcherd/meow_font.a" && ls *.o ) +cp "$TMP"/*.o meow_font.o 2>/dev/null || { echo "meow_font extract failed"; rm -rf "$TMP"; exit 1; } +rm -rf "$TMP" + +rm -f meow_ui.a +for f in $OBJS; do "$AR" rcs meow_ui.a "$f.o"; done +"$AR" rcs meow_ui.a meow_font.o +echo "== meow_ui.a ==" +ls -la meow_ui.a +"$AR" t meow_ui.a diff --git a/meow_ui.h b/meow_ui.h new file mode 100644 index 0000000..b4f0552 --- /dev/null +++ b/meow_ui.h @@ -0,0 +1,315 @@ +/* + * meow_ui.h — MeowOS 应用框架 SDK 公共 API(B1)。 + * + * 蓝本:MEOW_APP_SDK.md §3-§8。应用只调本头声明的 API;绝不 open/mmap/ioctl + * /dev/fb0、绝不 open /dev/input/event* —— 屏幕与输入由 meow_ui 接管。 + * + * 约定: + * - 坐标 0,0 = 左上;所有颜色为 24-bit RGB(0xRRGGBB),内部转 RGB565。 + * - 所有绘制先进离屏 back_buf,调用 meow_ui_flush()/flush_region() 才上屏。 + * - 文本字号:28(标题/状态栏)、20(正文/按钮)。 + * + * 与设计文档的偏差(实现期定案,见 MEOW_APP_SDK.md §12 更新): + * [D1] meow_ui_opts 由实现补充(宽/高覆写,0=自动读 /dev/fb0);文档只引用未定义。 + * [D2] meow_list_create 的数据源回调签名由实现定案(文档留占位注释)。 + * [D3] meow_img_blend 内部实现补充(见 meow_ui_internal.h),非公共 API 增量。 + * [D4] 增加 meow_theme_defaults()/meow_theme_current() 便于脱机(无 settings 文件)使用。 + */ +#ifndef MEOW_UI_H +#define MEOW_UI_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================ */ +/* 物理键 keycode(内核 input.h 常用值;KEEP 与 launcher/settings 一致) */ +/* ============================================================ */ +#define MEOW_KEY_POWER 116 +#define MEOW_KEY_VOLUMEDOWN 114 +#define MEOW_KEY_VOLUMEUP 115 +#define MEOW_KEY_PLAYPAUSE 164 +#define MEOW_KEY_PREVIOUS 19 /* KEY_PREVIOUSSONG */ +#define MEOW_KEY_NEXT 163 /* KEY_NEXTSONG */ + +/* 滑动方向(on_swipe 的 dir 参数) */ +#define MEOW_SWIPE_LEFT 0 +#define MEOW_SWIPE_RIGHT 1 +#define MEOW_SWIPE_UP 2 +#define MEOW_SWIPE_DOWN 3 + +/* ============================================================ */ +/* §3 绘制层 */ +/* ============================================================ */ +/* init 选项。width/height 为 0 表示自动读取 /dev/fb0 几何; + * 无 fb(宿主/qemu 离屏)时用 480×720 缺省([D1])。 */ +typedef struct meow_ui_opts { + int width; + int height; +} meow_ui_opts; + +int meow_ui_init(const meow_ui_opts *opts); /* 开 fb、读几何、载字库、建 back_buf;0=成功 */ +void meow_ui_shutdown(void); + +int meow_ui_width(void); /* 480 */ +int meow_ui_height(void); /* 720 */ + +void meow_ui_clear(uint32_t color); /* 整屏清(填背景色) */ +void meow_ui_put_px(int x, int y, uint32_t color); +void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color); +void meow_ui_draw_rect(int x, int y, int w, int h, uint32_t color); /* 空心框 */ +void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color); /* 圆角框 */ + +/* 文本(size=20/28) */ +void meow_ui_text(int x, int y, const char *utf8, int size, uint32_t color); +void meow_ui_text_right(int right_x, int y, const char *utf8, int size, uint32_t color); +void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color); +int meow_ui_text_width(const char *utf8, int size); +int meow_ui_text_height(int size); /* = meow_font_line_height */ + +/* 上屏:vsync + 原子 blit 脏区(内建脏区追踪) */ +void meow_ui_flush(void); +void meow_ui_flush_region(int x, int y, int w, int h); /* 只刷新指定区 */ + +/* 半透明(blend565:前景按 alpha 与背景混叠) */ +void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha); /* 0-255 */ + +/* ============================================================ */ +/* §4 图片层 */ +/* ============================================================ */ +typedef struct meow_img meow_img; /* 句柄,由 meow_img_* 管理,不透明 */ + +meow_img *meow_img_load_file(const char *path); /* 内部读文件 + stb 解码 */ +meow_img *meow_img_load_mem(const void *buf, int len); +void meow_img_free(meow_img *img); + +int meow_img_w(meow_img *img); +int meow_img_h(meow_img *img); + +void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */ +void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img); /* 最近邻缩放 */ +void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *img, int alpha); +void meow_ui_img_cover(int x, int y, int size, meow_img *img); /* 居中裁方 → size×size */ + +/* ============================================================ */ +/* §5 控件层 */ +/* ============================================================ */ +typedef struct meow_widget meow_widget; + +meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label); +meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val); +meow_widget *meow_switch_create(int x, int y, int on); +/* 列表数据源回调:idx 有效时填 label 并返回 1;越界返回 0([D2] 定案签名) */ +typedef int (*meow_list_source)(int idx, char *label, int len, void *ctx); +meow_widget *meow_list_create(int x, int y, int w, int h, int count, + meow_list_source src, void *ctx); +void meow_widget_free(meow_widget *w); + +void meow_widget_draw(meow_widget *w); /* 每次 flush 前调用 */ +int meow_widget_hit(meow_widget *w, int x, int y); /* 点是否落在控件内 */ + +void meow_slider_set(meow_widget *s, int val); +int meow_slider_get(meow_widget *s); +void meow_switch_set(meow_widget *w, int on); +int meow_switch_get(meow_widget *s); +int meow_btn_pressed(meow_widget *w); /* 应用在 on_tap 命中后标记按下 */ + +/* ============================================================ */ +/* §5.1 输入法(IME)—— 内置触屏软键盘(仅英文 QWERTY / 数字,ASCII) */ +/* ============================================================ */ +typedef enum { MEOW_IME_ASCII, MEOW_IME_NUM } meow_ime_mode; + +typedef struct meow_ime_ctx { + meow_ime_mode mode; + char buf[256]; /* 输入缓冲(初始值可预填) */ + int maxlen; /* 最大字符数(含 NUL 预算) */ + void (*on_change)(const char *text, void *ctx); /* 每次字符变化回调一次 */ + void *ctx; +} meow_ime_ctx; + +/* 阻塞:弹出键盘直到确认/取消。返回 1=确认(buf 已回填),0=取消。 */ +int meow_ui_text_input(meow_ime_ctx *ctx); + +/* 非阻塞:返回后 app 在 on_tick 里轮询 meow_ime_active()/meow_ime_text() */ +void meow_ui_text_input_async(meow_ime_ctx *ctx); +int meow_ime_active(void); +const char *meow_ime_text(void); + +/* ============================================================ */ +/* §6 输入层 / 生命周期(B1) */ +/* ============================================================ */ +typedef struct meow_ui_events { + void (*on_key)(int keycode, int is_down); /* M2.24:物理键为系统语义,SDK 不下发;回调保留(应用不映射键为确认/exit) */ + void (*on_tap)(int x, int y); /* 触摸抬起落点 */ + void (*on_swipe)(int dir, int dx, int dy); /* 左滑=返回(左缘起点+抬手触发) */ + void (*on_tick)(void); /* 空闲周期回调(动画/时钟) */ + void (*on_swipe_progress)(int x_start, int dx);/* 左缘返回手势过程实时回调;x_start=按下X, dx=当前X-按下X */ +} meow_ui_events; + +void meow_ui_run(const meow_ui_events *ev); /* 阻塞:事件循环,直到 meow_ui_request_exit */ +void meow_ui_request_exit(void); /* 请求退出(相当于返回/退出键) */ + +/* 顶部左滑进度条(视觉反馈):画一条 5px 高、从 x=0 到 x 的横条(主题 accent 色)。 + * x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */ +void meow_ui_swipe_hint(int x); + +/* ============================================================ */ +/* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */ +/* ============================================================ */ +/* 状态栏是 SDK 内置全局组件:所有界面默认显示(时钟/电量/WiFi/蓝牙/音量,slim ~40px), + * 数据由 SDK 内置 meow_ui_sys 读取器 1s 轻刷(与 launcher 同源单点一致)。 + * z 顺序最顶:meow_ui_flush() 上屏前自动把状态栏画进 back_buf 顶部行,覆盖非全屏内容; + * 非全屏 APP 需用 meow_ui_statusbar_height() 把内容下移(inset),否则被状态栏盖住。 */ + +/* 状态栏显隐(默认显示;APP 声明全屏则隐藏——沉浸)。 */ +void meow_ui_set_statusbar(int visible); /* 1=显示(默认) 0=隐藏(全屏) */ +int meow_ui_statusbar_visible(void); + +/* 全屏声明:等价 app.json fullscreen:1(外观上隐藏状态栏 + 左缘返回手势/进度条——沉浸)。 */ +void meow_ui_set_fullscreen(int on); + +/* 状态栏高度(px,供 APP 布局上移内容,避免被盖——status bar inset); + * 全屏/隐藏时返回 0。 */ +int meow_ui_statusbar_height(void); + +/* ============================================================ */ +/* §7 动画/时间(B1 最小可用) */ +/* ============================================================ */ +uint64_t meow_ui_time_ms(void); /* 单调毫秒时间戳 */ + +/* ============================================================ */ +/* §7.1 显示刷新率调度(A-1/A-2:从 launcher M2.16 迁入,归口 SDK,PERF_SCHEDULER 显示子域) */ +/* ============================================================ */ +/* 设目标档位 60/30/15(launcher 与 app 统一走 SDK 的 present 帧率门控一致)。 + * 同时把当前有效档对齐到目标(与 launcher settings_load 语义一致)。 */ +void meow_ui_set_refresh_rate(int r); + +/* 1=恒 max 不降档(性能优先);0=允许 governor 在 [15,30,60] 自适应降/升档。 */ +void meow_ui_set_refresh_always_max(int on); + +/* 当前有效帧率(governor 可能已降档;诊断用)。 */ +int meow_ui_effective_rate(void); + +/* 已呈现帧计数(验证 present 频率用)。 */ +unsigned long long meow_ui_frames_presented(void); + +/* ============================================================ */ +/* §8 主题/配置 */ +/* ============================================================ */ +typedef enum { MEOW_THEME_LIGHT, MEOW_THEME_DARK, MEOW_THEME_AUTO } meow_theme_mode; + +typedef struct meow_theme { + uint32_t bg; /* 背景色 */ + uint32_t fg; /* 前景/正文 */ + uint32_t accent; /* 强调色(按钮/选中) */ + uint32_t dim; /* 次要文字 */ + int font_title; /* 28 */ + int font_body; /* 20 */ +} meow_theme; + +const meow_theme *meow_theme_get(void); +void meow_theme_set_mode(meow_theme_mode m); +meow_theme_mode meow_theme_get_mode(void); + +typedef void (*meow_theme_listener)(meow_theme_mode m); +void meow_theme_add_listener(meow_theme_listener cb); +void meow_theme_remove_listener(meow_theme_listener cb); + +/* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */ +meow_theme meow_theme_defaults(meow_theme_mode m); + +/* ============================================================ */ +/* §9 应用日志(统一日志 API,M2.51 最小可用版) */ +/* ============================================================ */ +/* app 只需一行 meow_log();SDK 管「写哪/怎么写/过滤/降级/上限」: + * - 路径:/apps//logs/<启动时间>_.log + * (目录不存在 mkdir -p,按 app 分目录),每次启动=新文件,同一次启动内 O_APPEND 追加; + * - 级别过滤:lvl < meow_log_get_level() 丢弃;默认 MEOW_LOG_INFO; + * - 格式: [] message\n; + * - 上限:单文件 ≥2MB 自动开新文件追加 _;每 App 总量 ≥50MB 按期删最旧,防撑爆卡; + * - 无卡/写失败降级 stderr,不阻塞 app 不崩;线程安全。 + * 注意:MEOW_LOG_APP 由 launcher 注入;未注入时默认 app_id="app"。 */ +typedef enum { MEOW_LOG_DEBUG=0, MEOW_LOG_INFO, MEOW_LOG_WARN, MEOW_LOG_ERROR } meow_log_level; +void meow_log(meow_log_level lvl, const char *fmt, ...); /* app 一行调用,写到按 app 分离日志 */ +void meow_log_set_level(meow_log_level lvl); /* 可选:app 设本 app 级别;默认 MEOW_LOG_INFO */ +meow_log_level meow_log_get_level(void); + +/* ============================================================ */ +/* §9.1 应用通知(meow_notify_*,M2.53 最小可用版) */ +/* ============================================================ */ +/* app 只需声明类型 + meow_notify_send() 即可发通知,SDK 管配额/活动表/降级: + * - 类型注册:app 声明支持的类型(最多 MEOW_NOTIFY_MAX_TYPES 4 种); + * meow_notify_send 也会自动注册未声明类型(受同样 4 种上限约束); + * - 配额(NOTIFICATION_SYSTEM.md §7,2026-08-29 用户拍板):每 App 最多 4 种类型; + * 每类型活动(未过期)通知上限 4,满则丢最旧(替换);媒体类型单例(每 app 仅 + * 保留最近 1 条,重发替换——可多媒体 App); + * - 超时语义 [实现定案,沿用 NOTIFICATION_SYSTEM.md §2]:timeout_ms>0 = 过期毫秒数; + * 0 = 不自动过期(直到 meow_notify_cancel);-1 = 常驻(ONGOING),同样不自动过期; + * - 落点:本阶段 SDK 内部维护活动通知表 + 用 meow_log() 打日志作为落点;真实投递到 + * 通知中心 UI / D-Bus(org.freedesktop.Notifications)是后续任务——本结构已对齐 + * freedesktop 字段(summary/body/…)为此预留,但不写死后端; + * - 线程安全;不依赖 /dev/fb0、不依赖板子,可宿主编译。 */ +#define MEOW_NOTIFY_MAX_TYPES 4 /* 每 App 最多 4 种通知类型 */ +#define MEOW_NOTIFY_MAX_PER_TYPE 4 /* 每类型活动(未过期)通知上限 */ +#define MEOW_NOTIFY_MAX_MEDIA 1 /* 媒体通知每 App 仅 1 条(单例) */ +#define MEOW_NOTIFY_TIMEOUT_ONGOING (-1) /* 常驻(ONGOING),不自动过期 */ +#define MEOW_NOTIFY_TIMEOUT_NEVER 0 /* 不自动过期,直到取消 */ + +/* 通知分类(freedesktop.org Desktop Notifications 的 category 最小子集 [实现定案]) */ +typedef enum { + MEOW_NOTIFY_CAT_GENERAL = 0, /* 通用(默认) */ + MEOW_NOTIFY_CAT_MESSAGE = 1, /* 即时消息/聊天 */ + MEOW_NOTIFY_CAT_EMAIL = 2, /* 邮件 */ + MEOW_NOTIFY_CAT_EVENT = 3, /* 事件/日历提醒 */ + MEOW_NOTIFY_CAT_PROGRESS = 4, /* 进度/下载 */ + MEOW_NOTIFY_CAT_MEDIA = 5, /* 媒体播放(MPRIS;每 app 单例) */ + MEOW_NOTIFY_CAT_COUNT = 6, /* 边界哨兵(非合法类型) */ +} meow_notify_category; + +/* 紧急度(UI 视觉分级 + 提示音/震动强弱) */ +typedef enum { + MEOW_NOTIFY_URG_LOW = 0, /* 低:仅进通知中心,不打扰 */ + MEOW_NOTIFY_URG_NORMAL = 1, /* 普通 */ + MEOW_NOTIFY_URG_HIGH = 2, /* 高:顶部横幅 + 提示音 */ + MEOW_NOTIFY_URG_CRITICAL = 3, /* 紧急:需立刻注意 */ + MEOW_NOTIFY_URG_COUNT = 4, /* 边界哨兵(非合法值) */ +} meow_notify_urgency; + +/* 发送负载(对齐 freedesktop Notify() 字段;SDK 内部复制副本,不持 app 外部指针, + * app 只需保证 title/body/target_activity 指针在调用返回前有效)。 */ +typedef struct meow_notify { + meow_notify_category cat; /* 类型(未声明则按 4 种上限自动注册) */ + meow_notify_urgency urgency; /* 紧急度 */ + const char *title; /* summary:标题,单行(必填,NULL 则发送失败) */ + const char *body; /* body:正文,可多行(可 NULL) */ + int timeout_ms; /* 见上超时语义;推荐 MEOW_NOTIFY_TIMEOUT_* */ + const char *target_activity; /* 通知中心点击跳转的 Activity 名(可 NULL;M3 导航预留) */ + int progress; /* 进度 0-100;-1=不显示进度条(缺省 -1) */ +} meow_notify; + +/* 类型注册:app 声明支持的类型。0=成功;-1=已声明/已达 4 种上限/非法。 */ +int meow_notify_register_type(meow_notify_category cat); + +/* 发送一条通知。返回通知 id(用于 meow_notify_cancel);失败返回 0。 + * 同类型满 4 条时丢弃最旧(替换);媒体类型单例(重发替换同 app 上一条媒体通知)。 */ +uint32_t meow_notify_send(const meow_notify *n); + +/* 取消一条通知。1=成功;0=未找到(id 无效/已过期/已取消)。 */ +int meow_notify_cancel(uint32_t id); + +/* 查询当前活动(未过期、未取消)通知数/是否还有。 */ +int meow_notify_active_count(void); +int meow_notify_count_in_type(meow_notify_category cat); +int meow_notify_has_active(void); /* 1=尚有活动通知,0=无 */ + +/* 紧急度/分类 → 字符串("low"/"normal"/"high"/"critical"、"general"/"message"/…)。 */ +const char *meow_notify_urgency_str(meow_notify_urgency u); +const char *meow_notify_category_str(meow_notify_category cat); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_H */ diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c new file mode 100644 index 0000000..6397fef --- /dev/null +++ b/meow_ui_gfx.c @@ -0,0 +1,650 @@ +/* + * meow_ui_gfx.c — meow_ui 渲染后端(B1.0+B1.1)。 + * + * 独立实现:自建 fb 上下文(自 mmap /dev/fb0、自建 back_buf、自实现脏区+present), + * 不复用 launcher 的全局 g_dirty/fb(launcher 与 app 是不同进程)。机制参考 + * MEOW_APP_SDK.md §1/§2 与 launcher 的 M2.16 无闪烁地基,代码全部独立书写。 + * + * 抽象:所有绘制目标 = 离屏 back_buf(g_fb);meow_ui_flush() 时单次原子 + * vsync+blit 到 fb0(真实设备),或写出 PPM 截图(宿主/qemu 离屏,MEOW_UI_DUMP)。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" +#include "../meow_launcherd/meow_font.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 本设备实测 ioctl 号(勿与通用头交换) */ +#define FBIOGET_VSCREENINFO 0x4600 +#define FBIOGET_FSCREENINFO 0x4602 +#define FBIO_WAITFORVSYNC 0x4620 + +#define FB_DEV "/dev/fb0" +#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.png" +#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.idx" +#define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" +#define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" + +#define MUI_DEFAULT_W 480 +#define MUI_DEFAULT_H 720 + +/* 脏矩形上限(超出合并为全屏) */ +#define MUI_DIRTY_MAX 12 + +struct mu_rect { int x, y, w, h; }; + +/* ---- fb 上下文 ---- */ +static unsigned char *g_back_buf = NULL; /* 离屏(所有绘制目标) */ +static unsigned char *g_fb_mem = NULL; /* 真实 mmap 帧缓冲(present 落点;NULL=离屏) */ +static unsigned char *g_fb = NULL; /* 绘制指针 = g_back_buf */ +static int g_fb_fd = -1; +static int g_xres = MUI_DEFAULT_W, g_yres = MUI_DEFAULT_H; +static int g_bpp = 16; +static int g_line = 0; /* 每行字节数 */ +static int g_init_ok = 0; +static int g_mfont_ready = 0; + +static int g_dirty[/*MUI_DIRTY_MAX*/12][4]; /* [x,y,w,h] */ +static int g_ndirty = 0; +static int g_dirty_full = 1; + +static char *g_dump_path = NULL; + +/* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)---- + * meow_ui_flush()/present() 的 fps 档位与自适应降档 governor。launcher 不再自带 + * 一套帧率门控;launcher 与 app 统一走这里 → 全局观感/刷新率一致。 + * 设置接口:meow_ui_set_refresh_rate()/meow_ui_set_refresh_always_max()。 */ +static int g_refresh_rate_cfg = 60; /* 配置目标档 60/30/15(上限) */ +static int g_eff_rate = 60; /* 当前有效帧率(governor 可降档) */ +static int g_refresh_always_max = 0; /* 1=恒 max 不降档 */ +static unsigned long long g_last_present_ms = 0; +static unsigned long long g_frames_presented = 0; +static unsigned long long g_present_cost_sum = 0; +static int g_present_cost_n = 0; +static int g_gov_low = 0; + +/* 每档上限(refresh_always_max=1 时恒 max;0 时 governor 可在 [15,30,60] 内降/升档)。 */ +static int rate_bump(int r) { return (r == 15) ? 30 : (r == 30) ? 60 : 60; } +static int rate_drop(int r) { return (r == 60) ? 30 : (r == 30) ? 15 : 15; } + +/* 自适应降档 governor:统计每帧 present 耗时;连续 12 帧平均超预算 → 降一档; + * 连续 4 批(≈48 帧)平均远低于预算 → 升一档。refresh_always_max=1 时恒 max 不降。 */ +static void refresh_governor(unsigned long long cost_ms, int period) +{ + if (g_refresh_always_max || g_eff_rate <= 0) return; + g_present_cost_sum += cost_ms; g_present_cost_n++; + if (g_present_cost_n < 12) return; + unsigned long long avg = g_present_cost_sum / (unsigned int)g_present_cost_n; + if (avg > (unsigned long long)period) { + if (g_eff_rate > 15) g_eff_rate = rate_drop(g_eff_rate); + g_gov_low = 0; + } else if (avg * 2 < (unsigned long long)period) { + if (++g_gov_low >= 4) { + int nr = rate_bump(g_eff_rate); + if (nr <= g_refresh_rate_cfg) g_eff_rate = nr; + g_gov_low = 0; + } + } else g_gov_low = 0; + g_present_cost_sum = 0; g_present_cost_n = 0; +} + +/* ---- 工具 ---- */ +static uint64_t now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +uint64_t meow_ui_time_ms(void) { return now_ms(); } + +static uint16_t rgb565(uint32_t c) +{ + return (uint16_t)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F)); +} + +static uint8_t ex5(uint16_t v) { return (uint8_t)(((v) << 3) | ((v) >> 2)); } /* 5→8 */ +static uint8_t ex6(uint16_t v) { return (uint8_t)(((v) << 2) | ((v) >> 4)); } /* 6→8 */ + +static int rect_hit(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) +{ + return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; +} + +/* ================= 脏区追踪 ================= */ +static void dirty_full(void) { g_dirty_full = 1; g_ndirty = 0; } +static void dirty_reset(void) { g_ndirty = 0; g_dirty_full = 0; } + +static void dirty_add(int x, int y, int w, int h) +{ + if (g_dirty_full) return; + if (w <= 0 || h <= 0) return; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x >= g_xres || y >= g_yres) return; + if (x + w > g_xres) w = g_xres - x; + if (y + h > g_yres) h = g_yres - y; + if (w <= 0 || h <= 0) return; + for (int i = 0; i < g_ndirty; i++) { + if (rect_hit(g_dirty[i][0], g_dirty[i][1], g_dirty[i][2], g_dirty[i][3], x, y, w, h)) { + int nx1 = g_dirty[i][0] < x ? g_dirty[i][0] : x; + int ny1 = g_dirty[i][1] < y ? g_dirty[i][1] : y; + int nx2 = (g_dirty[i][0] + g_dirty[i][2]) > (x + w) ? (g_dirty[i][0] + g_dirty[i][2]) : (x + w); + int ny2 = (g_dirty[i][1] + g_dirty[i][3]) > (y + h) ? (g_dirty[i][1] + g_dirty[i][3]) : (y + h); + g_dirty[i][0] = nx1; g_dirty[i][1] = ny1; + g_dirty[i][2] = nx2 - nx1; g_dirty[i][3] = ny2 - ny1; + return; + } + } + if (g_ndirty >= MUI_DIRTY_MAX) { dirty_full(); return; } + g_dirty[g_ndirty][0] = x; g_dirty[g_ndirty][1] = y; + g_dirty[g_ndirty][2] = w; g_dirty[g_ndirty][3] = h; + g_ndirty++; +} + +void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } +void meow_gfx_mark_full(void) { dirty_full(); } + +/* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它, + * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ +unsigned char *meow_ui_back_buf(void) { return g_back_buf; } + +/* ================= 像素写入 ================= */ +static void px_write_rgb565(int x, int y, uint16_t c16) +{ + *(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2) = c16; +} +static uint16_t px_read_rgb565(int x, int y) +{ + return *(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2); +} + +static void put_px(int x, int y, uint32_t color) +{ + if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; + if (g_bpp == 16) { + px_write_rgb565(x, y, rgb565(color)); + } else { + unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4; + *(uint32_t *)p = (color << 8) | 0xFF; + } +} + +static void put_px_blend(int x, int y, uint32_t color, int alpha) +{ + if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; + uint16_t fg = rgb565(color); + if (g_bpp == 16) { + uint16_t bg = px_read_rgb565(x, y); + int ia = 255 - alpha; + unsigned r = ((((fg >> 11) & 0x1F) * alpha + ((bg >> 11) & 0x1F) * ia) / 255); + unsigned gg = ((((fg >> 5) & 0x3F) * alpha + ((bg >> 5) & 0x3F) * ia) / 255); + unsigned b = (((fg & 0x1F) * alpha + (bg & 0x1F) * ia) / 255); + px_write_rgb565(x, y, (uint16_t)((r << 11) | (gg << 5) | b)); + } else { + unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4; + uint32_t bg = (*(uint32_t *)p) >> 8; + int ia = 255 - alpha; + unsigned r = ((((color >> 16) & 0xFF) * alpha + ((bg >> 16) & 0xFF) * ia) / 255); + unsigned gg = ((((color >> 8) & 0xFF) * alpha + ((bg >> 8) & 0xFF) * ia) / 255); + unsigned b = (((color & 0xFF) * alpha + (bg & 0xFF) * ia) / 255); + *(uint32_t *)p = (r << 24) | (gg << 16) | (b << 8) | 0xFF; + } +} + +/* ================= 基本绘制 ================= */ +void meow_ui_clear(uint32_t color) +{ + if (!g_init_ok) return; + /* 快速整刷(16bit 用半字填充,可 memset 当色值由 32bit 拼接掩码) */ + if (g_bpp == 16) { + uint16_t c16 = rgb565(color); + uint32_t c32 = (uint32_t)c16 | ((uint32_t)c16 << 16); + for (int y = 0; y < g_yres; y++) { + uint32_t *row = (uint32_t *)(g_fb + (size_t)y * g_line); + int n = g_xres / 2; + for (int i = 0; i < n; i++) row[i] = c32; + if (g_xres & 1) px_write_rgb565(g_xres - 1, y, c16); + } + } else { + for (int y = 0; y < g_yres; y++) + for (int x = 0; x < g_xres; x++) put_px(x, y, color); + } + dirty_full(); +} + +void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color) +{ + if (!g_init_ok) return; + if (w <= 0 || h <= 0) return; + int x0 = x, y0 = y; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x >= g_xres || y >= g_yres) return; + if (x + w > g_xres) w = g_xres - x; + if (y + h > g_yres) h = g_yres - y; + if (w <= 0 || h <= 0) return; + if (g_bpp == 16) { + uint16_t c16 = rgb565(color); + for (int ry = y; ry < y + h; ry++) { + uint16_t *rp = (uint16_t *)(g_fb + (size_t)ry * g_line + (size_t)x * 2); + for (int rx = 0; rx < w; rx++) rp[rx] = c16; + } + } else { + for (int ry = y; ry < y + h; ry++) + for (int rx = x; rx < x + w; rx++) put_px(rx, ry, color); + } + dirty_add(x, y, w, h); +} + +void meow_ui_put_px(int x, int y, uint32_t color) +{ + if (!g_init_ok) return; + put_px(x, y, color); + dirty_add(x, y, 1, 1); +} + +void meow_ui_draw_rect(int x, int y, int w, int h, uint32_t color) +{ + if (w <= 0 || h <= 0) return; + meow_ui_fill_rect(x, y, w, 1, color); + meow_ui_fill_rect(x, y + h - 1, w, 1, color); + meow_ui_fill_rect(x, y, 1, h, color); + meow_ui_fill_rect(x + w - 1, y, 1, h, color); +} + +/* 圆角矩形区域判定(含内缩) */ +static int rr_inside(int px, int py, int x, int y, int w, int h, int r) +{ + if (px < x || px >= x + w || py < y || py >= y + h) return 0; + if (r <= 0) return 1; + if (w < 2 * r || h < 2 * r) return 1; /* 半径过大退化为矩形 */ + int cx1 = x + r, cy1 = y + r; + int cx2 = x + w - 1 - r, cy2 = y + h - 1 - r; + if (px < cx1 && py < cy1) return ((px - cx1) * (px - cx1) + (py - cy1) * (py - cy1)) <= r * r; + if (px > cx2 && py < cy1) return ((px - cx2) * (px - cx2) + (py - cy1) * (py - cy1)) <= r * r; + if (px < cx1 && py > cy2) return ((px - cx1) * (px - cx1) + (py - cy2) * (py - cy2)) <= r * r; + if (px > cx2 && py > cy2) return ((px - cx2) * (px - cx2) + (py - cy2) * (py - cy2)) <= r * r; + return 1; +} + +static int rr_border(int px, int py, int x, int y, int w, int h, int r) +{ + if (!rr_inside(px, py, x, y, w, h, r)) return 0; + int ir = r - 1; if (ir < 0) ir = 0; + if (w > 2 && h > 2 && rr_inside(px, py, x + 1, y + 1, w - 2, h - 2, ir)) return 0; + return 1; +} + +void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color) +{ + if (!g_init_ok) return; + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + for (int py = y1; py < y2; py++) + for (int px = x1; px < x2; px++) + if (rr_inside(px, py, x, y, w, h, r)) put_px(px, py, color); + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + +void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color) +{ + if (!g_init_ok) return; + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + for (int py = y1; py < y2; py++) + for (int px = x1; px < x2; px++) + if (rr_border(px, py, x, y, w, h, r)) put_px(px, py, color); + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + +void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha) +{ + if (!g_init_ok) return; + if (alpha <= 0) return; + if (alpha >= 255) { meow_ui_fill_rect(x, y, w, h, color); return; } + if (w <= 0 || h <= 0) return; + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x >= g_xres || y >= g_yres) return; + if (x + w > g_xres) w = g_xres - x; + if (y + h > g_yres) h = g_yres - y; + if (w <= 0 || h <= 0) return; + for (int ry = y; ry < y + h; ry++) + for (int rx = x; rx < x + w; rx++) put_px_blend(rx, ry, color, alpha); + dirty_add(x, y, w, h); +} + +/* ================= 左滑返回视觉条(GESTURE_BACK §2.3) ================= */ +#define MUI_HINT_H 5 +static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */ + +void meow_ui_swipe_hint(int x) +{ + if (!g_init_ok) return; + const meow_theme *th = meow_theme_get(); + /* 进度条画在状态栏下沿(z 在状态栏之下,不被全局状态栏覆盖)。 + * 有状态栏(非全屏)→ y = 状态栏高度;无状态栏(全屏/隐藏)→ y=0。 */ + int hy = meow_ui_statusbar_height(); + if (x > 0) { + if (x > g_xres) x = g_xres; + /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, + * 否则条只增不减(用户要求:右滑变长、左滑回缩实时可见)。 */ + if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->accent); + g_hint_last = x; + } else if (g_hint_last > 0) { + /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ + meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + g_hint_last = 0; + } + /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ +} + +/* ================= 5x7 回退点阵字(与 launcher/hello 一致的表,独立携带) ================= */ +static const unsigned char mfont5x7[][8] = { + {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00},{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00}, + {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00},{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00}, + {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00},{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00}, + {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00},{0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00}, + {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00},{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00}, + {0x0E,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00}, + {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00},{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00}, + {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00},{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00}, + {0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00},{0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00}, + {0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00},{0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00}, + {0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00},{0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00}, + {0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00},{0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00}, + {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00}, + {0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00},{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00}, + {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00},{0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00}, + {0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00}, + {0x11,0x11,0x11,0x15,0x15,0x1B,0x11,0x00},{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00}, + {0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00},{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00}, + {0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00},{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, +}; +#define FONT5X7_N (sizeof(mfont5x7)/sizeof(mfont5x7[0])) + +static int glyph_index(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'Z') return 10 + (c - 'A'); + if (c >= 'a' && c <= 'z') return 10 + (c - 'a'); + if (c == ' ') return 36; + if (c == '.') return 37; + if (c == '!') return 38; + if (c == '-' || c == '_') return 39; + return 36; +} + +static void draw_glyph5x7(int x, int y, char c, int scale, uint32_t color) +{ + const unsigned char *g = mfont5x7[glyph_index(c)]; + for (int row = 0; row < 7; row++) { + unsigned char bits = g[row]; + for (int col = 0; col < 5; col++) { + if (bits & (0x10 >> col)) { + for (int dy = 0; dy < scale; dy++) + for (int dx = 0; dx < scale; dx++) + put_px(x + col * scale + dx, y + row * scale + dy, color); + } + } + } +} + +static void draw_text5x7(int x, int y, const char *s, int scale, uint32_t color) +{ + while (*s) { draw_glyph5x7(x, y, *s, scale, color); x += 6 * scale; s++; } +} + +/* ================= 文本(字库优先,回退点阵) ================= */ +static int fallback_scale(int size) { return (size >= 16) ? 2 : 1; } + +static void text_draw(int x, int y, const char *s, int size, uint32_t color) +{ + if (!g_init_ok || !s) return; + if (g_mfont_ready && g_bpp == 16) { + meow_font_drawtext((uint16_t *)g_fb, x, y, s, rgb565(color), size); + int w = meow_font_width_text(s, size); + int hh = meow_font_line_height(size); + dirty_add(x, y, w, hh); + } else { + int sc = fallback_scale(size); + draw_text5x7(x, y, s, sc, color); + dirty_add(x, y, (int)strlen(s) * 6 * sc, 7 * sc); + } +} + +void meow_ui_text(int x, int y, const char *utf8, int size, uint32_t color) +{ text_draw(x, y, utf8, size, color); } + +void meow_ui_text_right(int right_x, int y, const char *utf8, int size, uint32_t color) +{ text_draw(right_x - meow_ui_text_width(utf8, size), y, utf8, size, color); } + +void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color) +{ text_draw(cx - meow_ui_text_width(utf8, size) / 2, y, utf8, size, color); } + +int meow_ui_text_width(const char *utf8, int size) +{ + if (!utf8) return 0; + if (g_mfont_ready) return meow_font_width_text(utf8, size); + return (int)strlen(utf8) * 6 * fallback_scale(size); +} + +int meow_ui_text_height(int size) +{ + if (g_mfont_ready) return meow_font_line_height(size); + return 7 * fallback_scale(size); +} + +/* ================= RGBA blit(图片层用) ================= */ +void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha) +{ + if (!g_init_ok || !rgba || w <= 0 || h <= 0 || alpha <= 0) return; + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + for (int py = y1; py < y2; py++) { + int sy = py - y; + for (int px = x1; px < x2; px++) { + int sx = px - x; + const uint8_t *p = rgba + ((size_t)sy * w + sx) * 4; + int a = (alpha * p[3]) / 255; + if (a <= 0) continue; + uint32_t c = ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; + if (a >= 255) put_px(px, py, c); + else put_px_blend(px, py, c, a); + } + } + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + +/* ================= present / 上屏 ================= */ +static void dump_ppm(void) +{ + if (!g_dump_path || !g_back_buf) return; + FILE *f = fopen(g_dump_path, "wb"); + if (!f) return; + fprintf(f, "P6\n%d %d\n255\n", g_xres, g_yres); + for (int y = 0; y < g_yres; y++) { + for (int x = 0; x < g_xres; x++) { + unsigned char r, g, b; + if (g_bpp == 16) { + uint16_t px = *(uint16_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 2); + r = ex5((px >> 11) & 0x1F); g = ex6((px >> 5) & 0x3F); b = ex5(px & 0x1F); + } else { + uint32_t px = *(uint32_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 4); + r = (px >> 16) & 0xFF; g = (px >> 8) & 0xFF; b = px & 0xFF; + } + fputc(r, f); fputc(g, f); fputc(b, f); + } + } + fclose(f); +} + +/* 顶部状态栏条是否被本次脏区触及(含整帧):触及或数据已变(force)才需要重画顶条。 */ +static int statusbar_touched(void) +{ + if (g_dirty_full) return 1; + for (int i = 0; i < g_ndirty; i++) + if (g_dirty[i][1] < MUI_SBAR_H) return 1; /* 脏矩形与顶条(前 MUI_SBAR_H 行)相交 */ + return 0; +} + +static void present(void) +{ + if (!g_init_ok || !g_back_buf) return; + if (!(g_dirty_full || g_ndirty > 0)) return; + /* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现; + * 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */ + unsigned long long now = now_ms(); + int period = (g_eff_rate > 0) ? (1000 / g_eff_rate) : 16; + 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 最顶)。 + * 全屏/隐藏时 active()==0 不画(沉浸),app 内容可占满整屏。触发条件: + * 数据已变(1s 轻刷/显隐切换) 或 顶条被 app 内容触及(含整帧清+绘,未 inset 时被顶条覆盖)。 + * 无需每帧重画——app 局部刷新不触及顶条时,back_buf/screen 顶条保持不变,省一次 blit。 */ + if (meow_ui_sys_statusbar_active() && + (meow_ui_sys_statusbar_force() || statusbar_touched())) + meow_ui_sys_statusbar_render(); + if (g_fb_mem) { + ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */ + unsigned long long tbg = now_ms(); + if (g_dirty_full) { + memcpy(g_fb_mem, g_back_buf, (size_t)g_line * (size_t)g_yres); + } else { + int bppb = g_bpp / 8; + for (int i = 0; i < g_ndirty; i++) { + int rw = g_dirty[i][2], ry = g_dirty[i][1]; + for (int yy = 0; yy < g_dirty[i][3]; yy++) { + size_t off = (size_t)(ry + yy) * g_line + (size_t)g_dirty[i][0] * bppb; + memcpy(g_fb_mem + off, g_back_buf + off, (size_t)rw * bppb); + } + } + } + refresh_governor(now_ms() - tbg, period); + } + g_last_present_ms = now; + g_frames_presented++; + if (g_dump_path) dump_ppm(); + dirty_reset(); +} + +void meow_ui_flush(void) { present(); } + +void meow_ui_flush_region(int x, int y, int w, int h) +{ dirty_add(x, y, w, h); present(); } + +/* ---- 帧率调度设置接口(launcher 与 app 共用;归口 PERF_SCHEDULER 显示刷新率子域)---- */ +/* 设目标档位(60/30/15):同时把当前有效档对齐到目标(与 launcher settings_load 语义一致)。 */ +void meow_ui_set_refresh_rate(int r) +{ + if (r != 60 && r != 30 && r != 15) return; + g_refresh_rate_cfg = r; + g_eff_rate = r; +} + +/* 1=恒 max 不降档(性能优先);0=允许 governor 在 [15,30,60] 内自适应降/升档。 */ +void meow_ui_set_refresh_always_max(int on) { g_refresh_always_max = on ? 1 : 0; } + +/* 当前有效帧率(governor 可能已降档;验证/诊断用)。 */ +int meow_ui_effective_rate(void) { return g_eff_rate; } + +/* 已呈现帧计数(验证 present 频率用)。 */ +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; } + +/* ================= 初始化/销毁 ================= */ +static int load_font(void) +{ + if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1; + if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1; + return 0; +} + +int meow_ui_init(const meow_ui_opts *opts) +{ + if (g_init_ok) return 0; + int fw = 0, fh = 0; + if (opts) { fw = opts->width; fh = opts->height; } + + int fd = open(FB_DEV, O_RDWR); + if (fd >= 0) { + unsigned char vinfo[160], finfo[68]; + memset(vinfo, 0, sizeof vinfo); + memset(finfo, 0, sizeof finfo); + if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) == 0 && + ioctl(fd, FBIOGET_FSCREENINFO, finfo) == 0) { + g_xres = *(uint32_t *)(vinfo + 0); + g_yres = *(uint32_t *)(vinfo + 4); + g_bpp = *(uint32_t *)(vinfo + 24); + g_line = *(uint32_t *)(finfo + 44); + if (!g_line) g_line = g_xres * (g_bpp / 8); + if (g_bpp != 16 && g_bpp != 32) g_bpp = 16; + size_t maplen = (size_t)g_line * (size_t)g_yres; + void *m = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (m != MAP_FAILED) { g_fb_mem = m; g_fb_fd = fd; } + else { close(fd); fd = -1; g_fb_mem = NULL; } + } else { close(fd); fd = -1; g_fb_mem = NULL; } + } + + /* host/qemu 无 fb:使用缺省/覆写几何,走离屏(present 只 dump) */ + if (g_fb_mem == NULL) { + if (g_xres <= 0 || g_xres > 4096) g_xres = MUI_DEFAULT_W; + if (g_yres <= 0 || g_yres > 4096) g_yres = MUI_DEFAULT_H; + if (g_bpp != 16 && g_bpp != 32) g_bpp = 16; + g_line = g_xres * (g_bpp / 8); + if (fd >= 0) { close(fd); fd = -1; } + } + if (fw > 0 && fw <= 4096) g_xres = fw; + if (fh > 0 && fh <= 4096) g_yres = fh; + g_line = g_xres * (g_bpp / 8); + + g_back_buf = malloc((size_t)g_line * (size_t)g_yres); + if (!g_back_buf) { if (g_fb_mem) munmap(g_fb_mem, 0); if (fd >= 0) close(fd); return -1; } + g_fb = g_back_buf; + + meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line); + g_mfont_ready = load_font(); + + const char *dp = getenv("MEOW_UI_DUMP"); + if (dp && *dp) g_dump_path = strdup(dp); + + g_init_ok = 1; + dirty_full(); + meow_ui_sys_init(); /* A-0:系统状态读取器首刷(电量/WiFi/蓝牙/音量),状态栏就绪 */ + return 0; +} + +void meow_ui_shutdown(void) +{ + if (!g_init_ok) return; + meow_font_unload(); + if (g_fb_mem) munmap(g_fb_mem, (size_t)g_line * (size_t)g_yres); + if (g_fb_fd >= 0) close(g_fb_fd); + if (g_back_buf) free(g_back_buf); + if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; } + g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL; + g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0; + dirty_reset(); +} + +int meow_ui_width(void) { return g_xres; } +int meow_ui_height(void) { return g_yres; } diff --git a/meow_ui_ime.c b/meow_ui_ime.c new file mode 100644 index 0000000..ff60b95 --- /dev/null +++ b/meow_ui_ime.c @@ -0,0 +1,235 @@ +/* + * meow_ui_ime.c — 内置触屏软键盘(B1.6)。仅英文 QWERTY / 数字,ASCII。 + * + * 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到确认(enter)/取消(esc|背景)。 + * 非阻塞:meow_ui_text_input_async(ctx) 打开后返回;meow_ui_run 在 on_tick 间隙 + * 随帧绘制并拦截触摸,app 用 meow_ime_active()/meow_ime_text() 轮询。 + * + * 键盘覆盖屏幕底部约 300px;点按输入;键位 44×36(touch target 达标)。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include + +#define IME_MAXKEYS 96 +#define KB_TOP 420 /* 面板顶(预览行起点) */ +#define GW 44 /* 键宽 */ +#define GH 36 /* 键高 */ +#define GAP 4 /* 键间距 */ +#define GCOL 48 /* 列步进 = GW+GAP */ + +typedef enum { K_CH, K_SHIFT, K_SPACE, K_BACK, K_ENTER, K_ESC } kkind; + +struct kbkey { int x, y, w, h; kkind kind; char ch; }; + +static struct kbkey g_keys[IME_MAXKEYS]; +static int g_nkeys = 0; + +static int g_ime_active = 0; +static int g_ime_done = 0; +static int g_ime_result = 0; +static int g_shift = 0; + +static meow_ime_ctx g_ime_ctx; /* 拷贝(on_change 回调用) */ +static meow_ime_ctx *g_ime_orig = NULL; /* 应用真实 ctx(buf 回填用) */ +static char g_ime_buf[256]; +static int g_maxlen = 255; + +/* ---- 布局 ---- */ +static void add_key(int x, int y, int w, int h, kkind k, char ch) +{ + if (g_nkeys >= IME_MAXKEYS) return; + g_keys[g_nkeys].x = x; g_keys[g_nkeys].y = y; + g_keys[g_nkeys].w = w; g_keys[g_nkeys].h = h; + g_keys[g_nkeys].kind = k; g_keys[g_nkeys].ch = ch; + g_nkeys++; +} + +/* 加入一行居中字符键;返回该行开始 x */ +static void add_ch_row(int y, const char *chars) +{ + int n = (int)strlen(chars); + int total = n * GW + (n - 1) * GAP; + int x = (480 - total) / 2; + for (int i = 0; i < n; i++) add_key(x + i * GCOL, y, GW, GH, K_CH, chars[i]); +} + +static void build_kb(meow_ime_mode mode) +{ + g_nkeys = 0; + int y = KB_TOP + 50; + if (mode == MEOW_IME_ASCII) { + add_ch_row(y, "1234567890"); + add_ch_row(y + GCOL, "QWERTYUIOP"); + /* 第 3 行:9 字母 + SHIFT */ + { + const char *r = "ASDFGHJKL"; + int n = (int)strlen(r); + int total = 10 * GW + 9 * GAP; + int x = (480 - total) / 2; + for (int i = 0; i < n; i++) add_key(x + i * GCOL, y + GCOL * 2, GW, GH, K_CH, r[i]); + add_key(x + 9 * GCOL, y + GCOL * 2, GW, GH, K_SHIFT, '^'); + } + /* 第 4 行:7 字母 + BACKSPACE */ + { + const char *r = "ZXCVBNM"; + int n = (int)strlen(r); + int total = 10 * GW + 9 * GAP; + int x = (480 - total) / 2; + for (int i = 0; i < n; i++) add_key(x + i * GCOL, y + GCOL * 3, GW, GH, K_CH, r[i]); + add_key(x + 7 * GCOL, y + GCOL * 3, (GW + GAP) * 3, GH, K_BACK, '<'); + } + /* 第 5 行:ESC + SPACE(宽) + ENTER */ + { + int total = 10 * GW + 9 * GAP; + int x = (480 - total) / 2; + add_key(x, y + GCOL * 4, GW, GH, K_ESC, 'X'); + add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' '); + add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>'); + } + } else { /* MEOW_IME_NUM */ + add_ch_row(y, "1234567890"); + add_ch_row(y + GCOL, "-/.:*#+="); + { + int total = 10 * GW + 9 * GAP; + int x = (480 - total) / 2; + add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); + add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); + add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_BACK, '<'); + add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_ENTER, '>'); /* 覆盖:回车 */ + } + /* 单独一行回车 */ + { + int total = 10 * GW + 9 * GAP; + int x = (480 - total) / 2; + add_key(x + GCOL * 4, y + GCOL * 3, 2 * GW + GAP, GH, K_ENTER, '>'); + } + } +} + +/* ---- 编辑动作 ---- */ +static int is_letter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } + +static void apply_key(const struct kbkey *k) +{ + switch (k->kind) { + case K_CH: { + char c = g_shift && is_letter(k->ch) ? (char)toupper((unsigned char)k->ch) : k->ch; + int len = (int)strlen(g_ime_buf); + if (len < g_maxlen - 1) { + g_ime_buf[len] = c; g_ime_buf[len + 1] = 0; + if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); + } + break; + } + case K_SHIFT: g_shift = !g_shift; break; + case K_SPACE: { + int len = (int)strlen(g_ime_buf); + if (len < g_maxlen - 1) { g_ime_buf[len] = ' '; g_ime_buf[len + 1] = 0; + if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); } + break; + } + case K_BACK: { + int len = (int)strlen(g_ime_buf); + if (len > 0) { g_ime_buf[len - 1] = 0; + if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); } + break; + } + case K_ENTER: g_ime_result = 1; g_ime_done = 1; break; + case K_ESC: g_ime_result = 0; g_ime_done = 1; break; + } +} + +/* ---- 绘制 ---- */ +void meow_ime_draw(void) +{ + if (!g_ime_active) return; + const meow_theme *th = meow_theme_get(); + /* 面板 */ + meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, 0x1C1C22); + /* 预览行 */ + meow_ui_fill_rect(0, KB_TOP, 480, 42, 0x2A2A33); + char preview[256]; + snprintf(preview, sizeof preview, "%s_", g_ime_buf); + meow_ui_text(14, KB_TOP + 9, preview, th->font_body, 0xFFFFFF); + /* 键 */ + for (int i = 0; i < g_nkeys; i++) { + const struct kbkey *k = &g_keys[i]; + int is_fn = (k->kind != K_CH); + uint32_t bg = is_fn ? 0x33333C : 0xFFFFFF; + meow_gfx_fill_round(k->x, k->y, k->w, k->h, 8, bg); + char label[2] = { k->ch, 0 }; + if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch) + : (char)tolower((unsigned char)k->ch); + uint32_t fg = is_fn ? 0xFFFFFF : 0x1A1A1E; + if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); } + else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); } + else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); } + } +} + +/* ---- 命中 ---- */ +int meow_ime_handle_tap(int x, int y) +{ + if (!g_ime_active || g_ime_done) return 0; + if (y < KB_TOP) { g_ime_result = 0; g_ime_done = 1; return 1; } /* 背景点=取消 */ + for (int i = 0; i < g_nkeys; i++) { + const struct kbkey *k = &g_keys[i]; + if (x >= k->x && x < k->x + k->w && y >= k->y && y < k->y + k->h) { + apply_key(k); + return 1; + } + } + return 1; /* 面板空隙:消费但不做 */ +} + +/* ---- 对外 ---- */ +int meow_ime_active(void) { return g_ime_active && !g_ime_done; } +const char *meow_ime_text(void) { return g_ime_buf; } + +void meow_ime_close(int result) +{ + if (!g_ime_active) return; + if (result && g_ime_orig) { + strncpy(g_ime_orig->buf, g_ime_buf, sizeof g_ime_orig->buf - 1); + g_ime_orig->buf[sizeof g_ime_orig->buf - 1] = 0; + } + g_ime_active = 0; g_ime_done = 0; +} + +void meow_ui_text_input_async(meow_ime_ctx *ctx) +{ + if (!ctx) return; + g_ime_orig = ctx; + g_ime_ctx = *ctx; /* 拷贝(on_change 用) */ + strncpy(g_ime_buf, ctx->buf, sizeof g_ime_buf - 1); + g_ime_buf[sizeof g_ime_buf - 1] = 0; + g_maxlen = ctx->maxlen > 0 ? ctx->maxlen : 255; + g_shift = 0; g_ime_result = 0; g_ime_done = 0; g_ime_active = 1; + build_kb(ctx->mode); + meow_input_start(); + meow_input_drain(); +} + +int meow_ui_text_input(meow_ime_ctx *ctx) +{ + if (!ctx) return 0; + meow_ui_text_input_async(ctx); + /* 阻塞循环:直到 done;on_change 由 handle_tap 触发 */ + while (meow_ime_active()) { + meow_input_ev e; + while (meow_input_pop(&e)) { + if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b); + } + meow_ime_draw(); + meow_ui_flush(); + usleep(16000); + } + int result = g_ime_result; + meow_ime_close(result); + return result; +} diff --git a/meow_ui_img.c b/meow_ui_img.c new file mode 100644 index 0000000..0e6b2e8 --- /dev/null +++ b/meow_ui_img.c @@ -0,0 +1,126 @@ +/* + * meow_ui_img.c — 图片层(B1.2)。基于 stb_image(单头文件、静态编入), + * 解 PNG/JPEG/BMP → RGBA,统一最近邻缩放/居中裁方。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "stb_image.h" + +#include +#include +#include + +struct meow_img { + int w, h; + unsigned char *rgba; /* 4 通道 straight alpha */ +}; + +static meow_img *img_alloc(int w, int h, unsigned char *rgba) +{ + if (w <= 0 || h <= 0 || !rgba) { if (rgba) stbi_image_free(rgba); return NULL; } + meow_img *im = (meow_img *)malloc(sizeof *im); + if (!im) { stbi_image_free(rgba); return NULL; } + im->w = w; im->h = h; im->rgba = rgba; + return im; +} + +meow_img *meow_img_load_mem(const void *buf, int len) +{ + if (!buf || len <= 0) return NULL; + int w = 0, h = 0, c = 0; + unsigned char *p = stbi_load_from_memory((const unsigned char *)buf, len, &w, &h, &c, 4); + if (!p) return NULL; + return img_alloc(w, h, p); +} + +meow_img *meow_img_load_file(const char *path) +{ + if (!path) return NULL; + int w = 0, h = 0, c = 0; + unsigned char *p = stbi_load(path, &w, &h, &c, 4); + if (!p) return NULL; + return img_alloc(w, h, p); +} + +void meow_img_free(meow_img *im) +{ + if (!im) return; + if (im->rgba) stbi_image_free(im->rgba); + free(im); +} + +int meow_img_w(meow_img *im) { return im ? im->w : 0; } +int meow_img_h(meow_img *im) { return im ? im->h : 0; } + +/* 最近邻采样缩放(cover=1 居中裁方),返回 dw*dh*4 临时 RGBA 缓冲 */ +static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover) +{ + if (!im || dw <= 0 || dh <= 0) return NULL; + unsigned char *out = (unsigned char *)malloc((size_t)dw * dh * 4); + if (!out) return NULL; + float s = 1.0f; + float ox = 0.0f, oy = 0.0f; + if (cover) { + float sx = (float)im->w / dw, sy = (float)im->h / dh; + s = sx > sy ? sx : sy; /* 铺满 → 可能外溢,居中裁 */ + float sw = im->w / s, sh = im->h / s; /* 目标显示寸(sw×sh) */ + ox = (float)(dw - sw) * 0.5f; + oy = (float)(dh - sh) * 0.5f; + if (ox < 0) ox = 0; if (oy < 0) oy = 0; + for (int dy = 0; dy < dh; dy++) { + int syi = (int)((dy + oy) / s); + if (syi < 0) syi = 0; if (syi >= im->h) syi = im->h - 1; + for (int dx = 0; dx < dw; dx++) { + int sxi = (int)((dx + ox) / s); + if (sxi < 0) sxi = 0; if (sxi >= im->w) sxi = im->w - 1; + memcpy(out + ((size_t)dy * dw + dx) * 4, + im->rgba + ((size_t)syi * im->w + sxi) * 4, 4); + } + } + } else { + for (int dy = 0; dy < dh; dy++) { + int syi = (int)((float)dy * im->h / dh); + if (syi >= im->h) syi = im->h - 1; + for (int dx = 0; dx < dw; dx++) { + int sxi = (int)((float)dx * im->w / dw); + if (sxi >= im->w) sxi = im->w - 1; + memcpy(out + ((size_t)dy * dw + dx) * 4, + im->rgba + ((size_t)syi * im->w + sxi) * 4, 4); + } + } + } + return out; +} + +void meow_ui_img(int x, int y, meow_img *im) +{ + if (!im) return; + meow_gfx_blit_rgba(x, y, im->w, im->h, im->rgba, 255); +} + +void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *im) +{ + if (!im) return; + unsigned char *tmp = scale_buf(im, w, h, 0); + if (!tmp) return; + meow_gfx_blit_rgba(x, y, w, h, tmp, 255); + free(tmp); +} + +void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *im, int alpha) +{ + if (!im) return; + unsigned char *tmp = scale_buf(im, w, h, 0); + if (!tmp) return; + meow_gfx_blit_rgba(x, y, w, h, tmp, alpha); + free(tmp); +} + +void meow_ui_img_cover(int x, int y, int size, meow_img *im) +{ + if (!im) return; + unsigned char *tmp = scale_buf(im, size, size, 1); + if (!tmp) return; + meow_gfx_blit_rgba(x, y, size, size, tmp, 255); + free(tmp); +} diff --git a/meow_ui_input.c b/meow_ui_input.c new file mode 100644 index 0000000..aa55289 --- /dev/null +++ b/meow_ui_input.c @@ -0,0 +1,333 @@ +/* + * meow_ui_input.c — 输入层(B1.4)。 + * + * 建立输入线程,读 /dev/input/event*(event1=触摸 goodix-ts、event0=物理键 + * md-gpio-keys、event2=ADC 键),翻译成语义事件(KEY/TAP/SWIPE)压入环形队列; + * meow_ui_run / IME 循环弹出分发。应用不再自读 event*。 + * + * 触摸:BTN_TOUCH 按下记录原点,抬起时位移 < 阈值 → TAP,否则按主导轴 → SWIPE。 + * 物理键:EV_KEY 值 1=按下 / 0=抬起(重复值 2 忽略)。M2.24 全面按键接管:电源/音量/播放/ + * 上下曲全部由系统(watchdog/launcher)消费,SDK 吞噬不发给 APP(应用只收 tap/swipe/tick)。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* linux input 事件常量(本设备实测) */ +#define EV_SYN 0 +#define EV_KEY 1 +#define EV_ABS 3 +#define BTN_TOUCH 0x14a +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_MT_POSITION_X 0x35 +#define ABS_MT_POSITION_Y 0x36 + +#define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */ +#define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ +#define QUEUE_N 128 +#define MAX_DEVS 8 + +static int g_thread_started = 0; +static pthread_t g_thread; +static volatile int g_thread_run = 1; + +/* 环形队列(互斥保护) */ +static meow_input_ev g_q[QUEUE_N]; +static volatile int g_qh = 0, g_qt = 0; +static pthread_mutex_t g_qm = PTHREAD_MUTEX_INITIALIZER; + +/* 事件循环状态 */ +static volatile int g_exit = 0; +static meow_ui_events g_events; +static int g_events_set = 0; +static uint64_t last_tick_ms = 0; +/* M2.22(C):启动 cooldown。应用刚进入时,用户点列表那几下(BTN_TOUCH 残留/抬手尾巴) + * 可能被当作「左缘滑动」→ 立即返回。窗口期丢弃 SWIPE(尤其 LEFT),仍接受 TAP。 */ +static uint64_t startup_cooldown_until = 0; +#define STARTUP_COOLDOWN_MS 500 + +static uint64_t now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +void meow_input_drain(void) +{ + pthread_mutex_lock(&g_qm); + g_qh = 0; g_qt = 0; + pthread_mutex_unlock(&g_qm); +} + +static void push_ev(int type, int a, int b) +{ + pthread_mutex_lock(&g_qm); + int n = (g_qt + 1) % QUEUE_N; + if (n == g_qh) { /* 满:丢弃 */ + pthread_mutex_unlock(&g_qm); + return; + } + g_q[g_qt].type = (uint8_t)type; g_q[g_qt].a = a; g_q[g_qt].b = b; + g_qt = n; + pthread_mutex_unlock(&g_qm); +} + +int meow_input_pop(meow_input_ev *ev) +{ + if (!ev) return 0; + pthread_mutex_lock(&g_qm); + if (g_qh == g_qt) { pthread_mutex_unlock(&g_qm); return 0; } + *ev = g_q[g_qh]; + g_qh = (g_qh + 1) % QUEUE_N; + pthread_mutex_unlock(&g_qm); + return 1; +} + +void meow_input_inject(int type, int a, int b) +{ + push_ev(type, a, b); +} + +/* ---- 触摸状态机(M2.22 B:手势级干净起点 + 原点严格锁定,杜绝残留坐标污染)---- + * 背景:goodix 事件时序不保证 BTN_TOUCH 与 ABS 的先后;旧版在按下边沿用 t_cx/t_cy + * 立即锁原点,而 t_cx/t_cy 是上一手势的残留 —— 上一次「中部 tap」留下的坐标被本次 + * 按下当原点(>EDGE_W),导致后续左缘侧滑不返回、进度条不出。且进程内不自愈。 + * 修法:每次抬手彻底清空全部轨迹状态;原点只在「本次按下之后」出现的首个有效 ABS + * 锁定(touch_update,gated 于 t_down && !t_origin_valid && x>=0 && y>=0)。 */ +static int t_down = 0; +static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate),等首个有效 ABS 才锁原点 */ +static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ +static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ +static int t_origin_valid = 0; /* 原点是否已锁定 */ + +/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 + * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ +static void touch_press(int x, int y) +{ + (void)x; (void)y; + t_down = 1; + t_press_valid = 1; + t_origin_valid = 0; + t_cx = -1; t_cy = -1; + t_dx = 0; t_dy = 0; +} +/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ +static void touch_update(int x, int y) +{ + if (t_down && !t_origin_valid && x >= 0 && y >= 0) { + t_dx = x; t_dy = y; t_origin_valid = 1; + } + if (x >= 0) t_cx = x; + if (y >= 0) t_cy = y; +} +static void touch_release(void) +{ + if (!t_down) return; + t_down = 0; t_press_valid = 0; + /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; } + t_origin_valid = 0; + int mdx = t_cx - t_dx, mdy = t_cy - t_dy; + int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; + /* 手势判定只在「抬手(release)边沿」做:按下/移动过程不触发 SWIPE,仅报 PROGRESS。 + * + * 返回手势("左滑"语义):原点确实在左缘 + 向右移动 ≥SWIPE_THRESH。设备实际为 + * 「左缘向内(右)划」≥65px(M2.21:90→65 用户实测更顺手;与 watchdog 兜底一致)。 + * 原点 t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且 t_cx/t_dx 已 + * 被手势级清空,绝不继承上一手势坐标,确保: + * ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W); + * ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」; + * ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。 + * SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。 + * + * 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP, + * 避免任意处左滑误退出)。纵向滑动→UP/DOWN 照旧。 */ + if (adx >= SWIPE_THRESH || ady >= SWIPE_THRESH) { + if (adx >= ady) { + if (t_dx >= 0 && t_dx < EDGE_W && (t_cx - t_dx) >= SWIPE_THRESH) { + push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, adx); /* 左缘返回手势 */ + } else if (mdx > 0) { + push_ev(MEOW_EV_SWIPE, MEOW_SWIPE_RIGHT, adx); /* 中部右滑:内容 */ + } else { + push_ev(MEOW_EV_TAP, t_cx, t_cy); /* 中部左滑:非返回 */ + } + } else { + int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP; + push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); + } + } else { + push_ev(MEOW_EV_TAP, t_cx, t_cy); + } + /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ + t_cx = t_cy = -1; t_dx = t_dy = 0; +} + +/* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ +static void *input_thread(void *arg) +{ + (void)arg; + struct pollfd fds[MAX_DEVS]; + int nfd = 0; + char dev[32]; + for (int i = 0; i < MAX_DEVS; i++) { + snprintf(dev, sizeof dev, "/dev/input/event%d", i); + int e = open(dev, O_RDONLY | O_NONBLOCK); + if (e >= 0) { fds[nfd].fd = e; fds[nfd].events = POLLIN; fds[nfd].revents = 0; nfd++; } + } + + while (g_thread_run) { + if (nfd == 0) { usleep(20000); continue; } + int r = poll(fds, nfd, 20); + if (r < 0) { if (errno == EINTR) continue; usleep(20000); continue; } + if (r == 0) continue; + for (int i = 0; i < nfd; i++) { + if (!(fds[i].revents & POLLIN)) continue; + unsigned char ev[16]; + while (read(fds[i].fd, ev, sizeof ev) == (ssize_t)sizeof ev) { + uint16_t type = *(uint16_t *)(ev + 8); + uint16_t code = *(uint16_t *)(ev + 10); + int32_t val = *(int32_t *)(ev + 12); + if (type == EV_ABS) { + if (code == ABS_X || code == ABS_MT_POSITION_X) { + if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */ + else t_cx = val; + /* 左滑进度实时上报(视觉条):原点确实在左缘(t_dxt_dx)。抬手触发仍由 touch_release 判定;这里只报过程。 + * t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已 + * 清空,杜绝「中部点一下出条 / 向左划也出条」。 */ + if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx) + push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx); + } + else if (code == ABS_Y || code == ABS_MT_POSITION_Y) { + if (t_down) touch_update(t_cx, val); else t_cy = val; + } + } else if (type == EV_KEY) { + if (code == BTN_TOUCH) { + if (val && !t_down) touch_press(t_cx, t_cy); + else if (!val && t_down) touch_release(); + } else if (code == MEOW_KEY_POWER || code == MEOW_KEY_VOLUMEDOWN || + code == MEOW_KEY_VOLUMEUP || code == MEOW_KEY_PLAYPAUSE || + code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 165) { + /* M2.22(A)/M2.24 纪律:物理键(电源 116 / 音量 114,115 / 播放 164 / + * 上一曲 19,165 / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费, + * 绝不下发 APP。应用不碰物理键、不把任何物理键映射成确认/exit/返回/翻页 + * (KEY_CONTROL_DESIGN §1.3 / APPS.md §3.4.1);APP 只接收 tap/swipe/tick。 + * 忽略该键,不发 MEOW_EV_KEY。 */ + } else if (val == 1) { /* 按下边沿(忽略重复=2)——仅未知键(本设备无) */ + push_ev(MEOW_EV_KEY, code, 1); + } else if (val == 0) { /* 抬起 */ + push_ev(MEOW_EV_KEY, code, 0); + } + } + } + } + } + for (int i = 0; i < nfd; i++) close(fds[i].fd); + return NULL; +} + +void meow_input_start(void) +{ + if (g_thread_started) return; + g_thread_started = 1; + g_thread_run = 1; + pthread_create(&g_thread, NULL, input_thread, NULL); +} + +/* ---- 事件循环 ---- */ +void meow_ui_request_exit(void) { g_exit = 1; } + +void meow_ui_run(const meow_ui_events *ev) +{ + if (ev) { g_events = *ev; g_events_set = 1; } + g_exit = 0; + meow_input_start(); + last_tick_ms = now_ms(); + startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */ + + while (!g_exit) { + meow_input_ev e; + int got = 0; + while (meow_input_pop(&e)) { + got = 1; + int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */ + if (e.type == MEOW_EV_TAP) { + /* 抬手未达标:清视觉条 */ + meow_ui_swipe_hint(-1); + meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); + if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue; + if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b); + } else if (e.type == MEOW_EV_KEY) { + if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b); + } else if (e.type == MEOW_EV_SWIPE) { + if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */ + /* 抬手达标:清视觉条(左滑返回),触发行消失 */ + meow_ui_swipe_hint(-1); + meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); + /* A-0 全局左缘返回(方案A): + * - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回); + * - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出); + * - 全屏/沉浸 → 无系统返回手势,仅 APP 自己处理。 + * 扩展点:阶段D M3 导航栈落地后,「默认退出」升级为「按 nav_stack 回退」。 */ + int sb_active = meow_ui_sys_statusbar_active(); + if (g_events_set && g_events.on_swipe) { + g_events.on_swipe(e.a, e.b, 0); + } else if (e.a == MEOW_SWIPE_LEFT && sb_active) { + meow_ui_request_exit(); + } + } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { + if (in_startup) continue; /* 启动窗口进度条静音 */ + int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ + /* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。 + * 全屏/沉浸不画系统条(无返回手势);APP 若注册 on_swipe_progress 仍可自绘。 */ + if (meow_ui_sys_statusbar_active()) meow_ui_swipe_hint(xc); + if (g_events_set && g_events.on_swipe_progress) + g_events.on_swipe_progress(x0, dx); + meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); + } + } + + uint64_t now = now_ms(); + if (g_events_set && g_events.on_tick) { + if (now - last_tick_ms >= 16) { + last_tick_ms = now; + g_events.on_tick(); + } + } + + /* A-0 全局状态栏 1s 轻刷:电量/WiFi/音量 + 时钟。时钟每秒变 → 重画顶条并上屏, + * 即使 APP 空闲(未主动 flush)也保持时钟走动。 */ + static uint64_t last_sys_ms = 0; + if (now - last_sys_ms >= 1000) { + last_sys_ms = now; + meow_ui_sys_refresh(); + if (meow_ui_sys_statusbar_active()) + meow_ui_flush_region(0, 0, meow_ui_width(), MUI_SBAR_H); + } + + if (meow_ime_active()) { /* 软键盘覆盖层随帧绘制(30fps 节流,省 CPU) */ + static uint64_t last_ime_ms = 0; + uint64_t n = now_ms(); + if (n - last_ime_ms >= 33) { + last_ime_ms = n; + meow_ime_draw(); + meow_ui_flush(); + } + if (!got) usleep(16000); + } else if (!got) { + usleep(16000); + } + } +} diff --git a/meow_ui_internal.h b/meow_ui_internal.h new file mode 100644 index 0000000..849c6d2 --- /dev/null +++ b/meow_ui_internal.h @@ -0,0 +1,65 @@ +/* + * meow_ui_internal.h — SDK 内部各 TU 共享声明(不随 meow_ui.h 发布给应用)。 + * 仅供 meow_ui_*.c 之间协作:渲染后端底层能力 + 输入事件队列 + 主题取用。 + */ +#ifndef MEOW_UI_INTERNAL_H +#define MEOW_UI_INTERNAL_H + +#include +#include "meow_ui.h" + +/* ---- 渲染后端(meow_ui_gfx.c)提供 ---- */ +/* 把 RGBA 像素(stb_image 输出,4 通道 straight alpha)blit 进 back_buf; + * alpha 为全局不透明度 0-255(255 时仍用像素自身 alpha 做逐像素混合)。 */ +void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha); + +/* 标记一个区域脏(供非标准绘制使用)。 */ +void meow_gfx_mark_dirty(int x, int y, int w, int h); + +/* 标记整帧脏(launcher 手动脏模型桥接:自绘 raw 像素后标全屏,present 走 SDK)。 */ +void meow_gfx_mark_full(void); + +/* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */ +unsigned char *meow_ui_back_buf(void); + +/* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */ +void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color); + +/* 是否运行于真实 fb 之上(0 = 头less/离屏,qemu/宿主)。 */ +int meow_gfx_is_device(void); + +/* 中文/日文字库(mfont)是否已加载(状态栏「蓝牙」CJK 与回退 "BT" 二选一)。 */ +int meow_gfx_mfont_ready(void); + +/* ---- 输入层(meow_ui_input.c)---- */ +/* 语义事件:由输入线程解析后压入队列,run/IME 循环弹出。 */ +#define MEOW_EV_KEY 1 /* a=keycode, b=1 按下 / 0 抬起 */ +#define MEOW_EV_TAP 2 /* a=x, b=y(触摸抬起落点) */ +#define MEOW_EV_SWIPE 3 /* a=MEOW_SWIPE_*, b=距离(px) */ +#define MEOW_EV_SWIPE_PROGRESS 4 /* a=按下X(t_dx), b=当前X(t_cx);dx=b-a(>0=左缘向内划=返回手势) */ + +typedef struct meow_input_ev { + uint8_t type; + int a, b; +} meow_input_ev; + +/* 启动输入线程(幂等)。无设备时仅空转。 */ +void meow_input_start(void); +/* 从队列弹出一个事件;无事件返回 0。 */ +int meow_input_pop(meow_input_ev *ev); +/* 注入一条语义事件(宿主验证手势分发/进度条用;设备上无调用方)。 */ +void meow_input_inject(int type, int a, int b); +/* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ +void meow_input_drain(void); + +/* ---- IME(meow_ui_ime.c)---- */ +/* 当前是否显示软键盘。 */ +int meow_ime_active(void); +/* 软键盘拦截一次触摸。返回 1=已消费(供 IME 转键/提交),0=未命中(透传给 app)。 */ +int meow_ime_handle_tap(int x, int y); +/* 软键盘绘制覆盖层(每次 flush 前调用;覆盖底部约 40%)。 */ +void meow_ime_draw(void); +/* 关闭 IME 并交付最终结果(result: 1=确认 0=取消)。 */ +void meow_ime_close(int result); + +#endif /* MEOW_UI_INTERNAL_H */ diff --git a/meow_ui_log.c b/meow_ui_log.c new file mode 100644 index 0000000..39342c5 --- /dev/null +++ b/meow_ui_log.c @@ -0,0 +1,279 @@ +/* + * meow_ui_log.c — MeowOS 应用统一日志 API(M2.52 版)。 + * + * 背景:此前 app 各自造轮子(如 hello.c hellolog() 自读 MEOW_LOG_DIR + snprintf + + * fopen/fprintf),重复、易错、无分离/级别。本文件实现 SDK 统一 meow_log(): + * + * - app 一行调用 meow_log(MEOW_LOG_INFO, "start")(见 meow_ui.h); + * - SDK 启动时读 env:MEOW_LOG_DIR(日志目录)、MEOW_LOG_APP(app_id,无则 "app")、 + * MEOW_LOG_LEVEL(本 app 级别,无则默认 INFO)→ 写 + * `/apps//logs/<启动时间>_[._].log`(目录自动 mkdir -p), + * 按 app 分目录 + 每次启动新文件(不同启动=不同文件,同一次启动内 O_APPEND 追加), + * 级别过滤(lvl < level 丢弃); + * - 格式:` [] message\n`(meow_ui_time_ms() 取毫秒); + * - 无 log_dir(无卡)/目录不可建/写失败 → 降级 stderr,不阻塞 app 不崩。 + * + * 上限(用户定,防撑爆 TF 卡): + * - 单文件 ≥ MEOW_LOG_FILE_MAX(2MB)→ 关当前 fd,开新文件并追加序号 `_`; + * - 每 App 日志总量 ≥ MEOW_LOG_TOTAL_MAX(50MB)→ 按期删最旧文件(按 mtime), + * 节流(每 MEOW_LOG_CLEANUP_EVERY 次写入检查一次,另在首次打开/每次轮转时检查), + * 避免高频日志时逐行 readdir/stat。 + * + * 约定:与 meow_ui_gfx.c 同风格(M2.x 中文注释)。线程安全(pthread_mutex,launcher + * 当前台/后台进程都会在已链 -lpthread 的应用里调用)。 + */ +#include "meow_ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 单行消息上限(超长截断) */ +#define MEOW_LOG_LINE_MAX 1024 + +/* 日志上限:单文件 2MB 开新文件;每 App 总量 50MB 删最旧 */ +#define MEOW_LOG_FILE_MAX (2 * 1024 * 1024) +#define MEOW_LOG_TOTAL_MAX (50 * 1024 * 1024) +#define MEOW_LOG_CLEANUP_MAX 128 /* 一次清理最多扫描文件数 */ +#define MEOW_LOG_CLEANUP_EVERY 2048 /* 每 N 次写入做一次总量清理(节流) */ + +/* 级别字符串表(与 meow_log_level 序号对齐) */ +static const char *g_lvl_str[] = { "DEBUG", "INFO", "WARN", "ERROR" }; + +/* 内部状态(各字段约定见头注释) */ +static pthread_mutex_t g_log_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t g_log_once = PTHREAD_ONCE_INIT; + +static char g_app_id[64] = "app"; /* env MEOW_LOG_APP,无则 "app" */ +static char g_log_dir[256] = ""; /* env MEOW_LOG_DIR,无/空则降级 stdio */ +static meow_log_level g_level = MEOW_LOG_INFO; /* 默认 INFO */ + +/* M2.52:日志文件为 `/apps//logs/_[._].log`。 + * g_log_base 存不含序号后缀的路径前缀 `/apps//logs/_`; + * g_log_seq 为轮转序号(0=首个文件无后缀;>0 追加 `_`)。两者均在 log_init() + * (pthread_once 那次)里生成一次。 */ +static char g_log_base[384] = ""; +static char g_log_file[512] = ""; +static int g_log_seq = 0; + +static int g_log_fd = -1; /* 当前日志 fd;-1=未开 */ +static int g_log_fallback = 0; /* 1=无卡/写失败,降级 stderr */ +static int g_log_open_tried = 0; /* 是否已尝试开首个文件(只试一次,失败则降级) */ +static unsigned long g_write_count = 0; /* 累计写入次数(周期总量清理节流) */ + +/* 由 g_log_base + g_log_seq 重建当前文件路径(seq=0 无后缀,>0 追加 _)。 */ +static void log_rebuild_file(void) +{ + if (g_log_seq <= 0) + snprintf(g_log_file, sizeof g_log_file, "%s.log", g_log_base); + else + snprintf(g_log_file, sizeof g_log_file, "%s_%d.log", g_log_base, g_log_seq); +} + +/* 一次性初始化:读 env 决定 app_id/log_dir/level;构建 g_log_base 与首个 g_log_file。 + * (pthread_once 保证仅一次。) */ +static void log_init(void) +{ + const char *s; + + /* app_id:env MEOW_LOG_APP,无则 "app" */ + s = getenv("MEOW_LOG_APP"); + if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); + + /* log_dir:env MEOW_LOG_DIR,无/空则 ""(降级 stdio) */ + s = getenv("MEOW_LOG_DIR"); + if (s && s[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", s); + + /* M2.52:构建完整前缀 `/apps//logs/_`。 + * ts 用 localtime 的 %Y%m%d_%H%M%S(启动时刻);尾拼 _ 防同秒重复启动撞名, + * 从而每次启动=新文件,同一次启动内 O_APPEND 追加。若 log_dir 为空(无卡) + * 则跳过(log_open 降级 stderr)。 */ + if (g_log_dir[0]) { + char ts[32]; + time_t now = time(NULL); + struct tm tmnow; + localtime_r(&now, &tmnow); + strftime(ts, sizeof ts, "%Y%m%d_%H%M%S", &tmnow); + snprintf(g_log_base, sizeof g_log_base, "%s/apps/%s/logs/%s_%d", + g_log_dir, g_app_id, ts, (int)getpid()); + log_rebuild_file(); + } + + /* level:env MEOW_LOG_LEVEL 可选覆盖(系统下发;默认 INFO) */ + s = getenv("MEOW_LOG_LEVEL"); + if (s && s[0]) { + if (strcmp(s, "DEBUG") == 0) g_level = MEOW_LOG_DEBUG; + else if (strcmp(s, "WARN") == 0) g_level = MEOW_LOG_WARN; + else if (strcmp(s, "ERROR") == 0) g_level = MEOW_LOG_ERROR; + else g_level = MEOW_LOG_INFO; + } +} + +/* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 */ +static int log_mkdir_p(const char *path) +{ + char tmp[256]; + size_t len, i; + if (!path || !path[0] || strlen(path) >= sizeof tmp) return -1; + len = strlen(path); + memcpy(tmp, path, len + 1); + if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; /* 去尾斜杠 */ + for (i = 1; i <= strlen(tmp); i++) { + if (tmp[i] == '/' || tmp[i] == '\0') { + char ch = tmp[i]; + tmp[i] = '\0'; + if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1; + tmp[i] = ch; + } + } + return 0; +} + +/* M2.52:对 `/apps//logs` 增量 mkdir -p,并打开 g_log_file + * (O_APPEND|O_CREAT;同一次启动内追加,不同启动=不同文件)。失败置 g_log_fallback。 + * 首次由 meow_log 经 g_log_open_tried 守卫调用;轮转后由 meow_log 再次调用(目录已存在, + * mkdir_p 幂等)。仅在持有 g_log_mu 时调用(g_log_base 已构建)。 */ +static void log_open(void) +{ + char logs[512]; + if (!g_log_dir[0] || !g_log_file[0]) { g_log_fallback = 1; return; } + if (snprintf(logs, sizeof logs, "%s/apps/%s/logs", g_log_dir, g_app_id) >= (int)sizeof logs) { + g_log_fallback = 1; return; + } + if (log_mkdir_p(logs) != 0) { g_log_fallback = 1; return; } + g_log_fd = open(g_log_file, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (g_log_fd < 0) g_log_fallback = 1; +} + +/* 清理用文件记录(按 mtime 升序删最旧) */ +typedef struct { char name[384]; time_t mtime; long size; } log_spill_t; + +static int log_spill_cmp(const void *a, const void *b) +{ + const log_spill_t *fa = a, *fb = b; + if (fa->mtime < fb->mtime) return -1; + if (fa->mtime > fb->mtime) return 1; + return 0; +} + +/* 每 App 总量清理:扫描 `/apps//logs/` 下所有 .log(跳过当前正写的 + * g_log_file),累计大小,超过 MEOW_LOG_TOTAL_MAX 则按 mtime 从最旧删到 ≤ 阈值。 + * 只规整本 App 目录(进程隔离),节流调用(见 meow_log)。仅在持有 g_log_mu 时调用。 */ +static void log_cleanup_old(void) +{ + DIR *d; + struct dirent *e; + char logs[512], path[384]; + log_spill_t files[MEOW_LOG_CLEANUP_MAX]; + int n = 0, i; + long total = 0; + + if (!g_log_dir[0] || !g_log_file[0]) return; + if (snprintf(logs, sizeof logs, "%s/apps/%s/logs", g_log_dir, g_app_id) + >= (int)sizeof logs) return; + d = opendir(logs); + if (!d) return; + + while ((e = readdir(d)) != NULL) { + struct stat st; + size_t nl; + if (e->d_name[0] == '.') continue; + nl = strlen(e->d_name); + if (nl < 5 || strcmp(e->d_name + nl - 4, ".log") != 0) continue; /* 只看 .log */ + if (snprintf(path, sizeof path, "%s/%s", logs, e->d_name) >= (int)sizeof path) continue; + if (strcmp(path, g_log_file) == 0) continue; /* 跳过当前正写文件,避免删 unlink 后仍写 */ + if (stat(path, &st) != 0) continue; + if (!S_ISREG(st.st_mode)) continue; + if (n >= MEOW_LOG_CLEANUP_MAX) break; + snprintf(files[n].name, sizeof files[n].name, "%s", path); + files[n].mtime = st.st_mtime; + files[n].size = (long)st.st_size; + total += (long)st.st_size; + n++; + } + closedir(d); + + if (total <= MEOW_LOG_TOTAL_MAX) return; + qsort(files, (size_t)n, sizeof files[0], log_spill_cmp); + for (i = 0; i < n && total > MEOW_LOG_TOTAL_MAX; i++) { + if (unlink(files[i].name) == 0) total -= files[i].size; + } +} + +void meow_log(meow_log_level lvl, const char *fmt, ...) +{ + char body[MEOW_LOG_LINE_MAX]; + char line[MEOW_LOG_LINE_MAX + 96]; + const char *lvl_s; + va_list ap; + int n; + + pthread_once(&g_log_once, log_init); + + pthread_mutex_lock(&g_log_mu); + if (lvl < g_level) { pthread_mutex_unlock(&g_log_mu); return; } /* 级别过滤 */ + + va_start(ap, fmt); + vsnprintf(body, sizeof body, fmt, ap); + va_end(ap); + body[sizeof body - 1] = '\0'; + + if (!g_log_open_tried) { g_log_open_tried = 1; log_open(); } + if (g_log_fd >= 0 && g_write_count == 0) log_cleanup_old(); /* 首次打开即清一次 */ + + /* 级别名钳位(防 app 传越界枚举值触发 OOB 读) */ + lvl_s = (lvl >= 0 && lvl <= MEOW_LOG_ERROR) ? g_lvl_str[lvl] + : g_lvl_str[MEOW_LOG_ERROR]; + + if (g_log_fd >= 0) { + struct stat fst; + /* 单文件超限 → 轮转开新文件(_ 递增);fstat 每次写前检查(日志低频,开销可忽略) */ + if (fstat(g_log_fd, &fst) == 0 && fst.st_size >= (off_t)MEOW_LOG_FILE_MAX) { + close(g_log_fd); + g_log_fd = -1; + g_log_seq++; + log_rebuild_file(); + log_open(); + if (g_log_fd >= 0) log_cleanup_old(); /* 轮转后顺带清一次 */ + } + if (g_log_fd >= 0) { + n = snprintf(line, sizeof line, "%llu [%s] %s %s\n", + (unsigned long long)meow_ui_time_ms(), + g_app_id, lvl_s, body); + if (n > 0) { ssize_t wr = write(g_log_fd, line, (size_t)n); (void)wr; } + } + /* 周期总量清理(节流,避免高频日志逐行 readdir/stat) */ + if (++g_write_count % MEOW_LOG_CLEANUP_EVERY == 0) log_cleanup_old(); + } else { + /* 降级 stderr(无卡/写失败):不阻塞 app,不崩 */ + fprintf(stderr, "[%s] %s %s\n", g_app_id, lvl_s, body); + } + pthread_mutex_unlock(&g_log_mu); +} + +void meow_log_set_level(meow_log_level lvl) +{ + pthread_once(&g_log_once, log_init); + pthread_mutex_lock(&g_log_mu); + g_level = lvl; + pthread_mutex_unlock(&g_log_mu); +} + +meow_log_level meow_log_get_level(void) +{ + meow_log_level l; + pthread_once(&g_log_once, log_init); + pthread_mutex_lock(&g_log_mu); + l = g_level; + pthread_mutex_unlock(&g_log_mu); + return l; +} diff --git a/meow_ui_notify.c b/meow_ui_notify.c new file mode 100644 index 0000000..2a02957 --- /dev/null +++ b/meow_ui_notify.c @@ -0,0 +1,253 @@ +/* + * meow_ui_notify.c — MeowOS 应用通知 API(meow_notify_*,M2.53 最小可用版)。 + * + * 定位:app 只需声明类型 + meow_notify_send() 即可发通知,SDK 管配额/活动表/降级。 + * 本切片为「SDK 侧应用可调用 API」第一个纵向切片——只做 SDK 层;通知中心 UI、 + * D-Bus 守护(org.freedesktop.Notifications)、TF 转储是后续独立任务。 + * + * 行为约定(NOTIFICATION_SYSTEM.md §2/§7,2026-08-29 用户拍板): + * - 类型注册:app 声明支持的类型,最多 MEOW_NOTIFY_MAX_TYPES(4)种; + * meow_notify_send 也会自动注册未声明类型(受同样 4 种上限约束)。 + * - 配额:每类型活动(未过期)通知上限 MEOW_NOTIFY_MAX_PER_TYPE(4),满则丢最旧 + * (替换);媒体类型单例(每 app 仅保留最近 1 条,重发替换——可多媒体 App)。 + * - 超时语义 [实现定案]:timeout_ms > 0 为该毫秒数后过期;0 = 不自动过期(直到 + * meow_notify_cancel);-1 = 常驻(ONGOING),同样不自动过期。 + * - 落点:本阶段内部维护活动通知表 + 用既有 meow_log() 打日志作为落点;真实投递到 + * 通知中心 UI / D-Bus 是后续任务——meow_notify 结构已对齐 freedesktop 字段 + * (summary/body/…)为此预留,但不写死后端。 + * + * 线程安全:pthread_mutex(同 meow_ui_log.c,launcher 前后台进程都会在已链 -lpthread + * 的应用里调用)。不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 + * meow_ui_time_ms()(单调毫秒)与 meow_log()(统一日志)两个既有 SDK 能力。 + */ +#include "meow_ui.h" + +#include +#include +#include +#include + +/* ---- 内部活动表尺寸(配额见 meow_ui.h) ---- */ +#define MEOW_NOTIFY_MAX_ACTIVE (MEOW_NOTIFY_MAX_TYPES * MEOW_NOTIFY_MAX_PER_TYPE) + +/* 活动通知槽(内部;标题/正文等复制副本,不持 app 外部指针,避免栈字符串悬垂) */ +typedef struct { + uint32_t id; /* 唯一 id;0=空闲槽 */ + int active; /* 1=活动(未取消未过期),0=空闲 */ + meow_notify_category cat; + meow_notify_urgency urg; + int timeout_ms; + uint64_t created_ms; + char title[64]; + char body[128]; + char target_activity[32]; + int progress; /* 0-100;-1=无进度条 */ +} notify_slot_t; + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static notify_slot_t g_slots[MEOW_NOTIFY_MAX_ACTIVE]; +static int g_cats[MEOW_NOTIFY_MAX_TYPES]; /* 已声明类型(按注册顺序) */ +static int g_cat_n = 0; +static uint32_t g_next_id = 1; + +/* 字符串表(与枚举序号对齐) */ +static const char *g_urg_str[] = { "low", "normal", "high", "critical" }; +static const char *g_cat_str[] = { "general", "message", "email", "event", "progress", "media" }; + +/* ---- 内部工具(以下均须持有 g_mu 时调用) ---- */ + +/* 该分类当前活动数。 */ +static int notify_count_cat(meow_notify_category cat) +{ + int i, n = 0; + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active && g_slots[i].cat == cat) n++; + return n; +} + +/* 该分类是否已被 app 声明。 */ +static int notify_is_declared(meow_notify_category cat) +{ + int i; + for (i = 0; i < g_cat_n; i++) + if (g_cats[i] == (int)cat) return 1; + return 0; +} + +/* 清掉已过期活动槽(TTL 用 meow_ui_time_ms() 单调毫秒)。 */ +static void notify_purge_expired(void) +{ + uint64_t now = meow_ui_time_ms(); + int i; + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) { + notify_slot_t *s = &g_slots[i]; + if (!s->active) continue; + if (s->timeout_ms > 0 && (now - s->created_ms) >= (uint64_t)s->timeout_ms) { + s->active = 0; + s->id = 0; + } + } +} + +/* 填充一个活动槽(改前须确认槽空闲或已被显式复用)。 */ +static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uint64_t now) +{ + s->id = id; + s->active = 1; + s->cat = n->cat; + s->urg = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT) + ? n->urgency : MEOW_NOTIFY_URG_NORMAL; + s->timeout_ms = n->timeout_ms; + s->created_ms = now; + snprintf(s->title, sizeof s->title, "%s", n->title ? n->title : ""); + snprintf(s->body, sizeof s->body, "%s", n->body ? n->body : ""); + snprintf(s->target_activity, sizeof s->target_activity, "%s", + n->target_activity ? n->target_activity : ""); + s->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); +} + +/* 落点:本阶段用统一日志标出一条投递;真实后端(通知中心 UI / D-Bus)后续替换此处。 */ +static void notify_deliver(const notify_slot_t *s, const char *ev) +{ + meow_log(MEOW_LOG_INFO, "[notify] %s id=%u type=%s urgency=%s title=%s", + ev, (unsigned)s->id, meow_notify_category_str(s->cat), + meow_notify_urgency_str(s->urg), s->title); +} + +/* ============================================================ */ +/* 公共 API(见 meow_ui.h §9.1) */ +/* ============================================================ */ + +int meow_notify_register_type(meow_notify_category cat) +{ + int rc = 0; + if ((int)cat < 0 || (int)cat >= (int)MEOW_NOTIFY_CAT_COUNT) return -1; + pthread_mutex_lock(&g_mu); + if (notify_is_declared(cat)) rc = -1; /* 重复声明 */ + else if (g_cat_n >= MEOW_NOTIFY_MAX_TYPES) rc = -1; /* 超 4 种上限 */ + else g_cats[g_cat_n++] = (int)cat; + pthread_mutex_unlock(&g_mu); + return rc; +} + +uint32_t meow_notify_send(const meow_notify *n) +{ + uint32_t id; + uint64_t now, oldest; + int i, slot_idx, count, oldest_idx; + + if (!n || (int)n->cat < 0 || (int)n->cat >= (int)MEOW_NOTIFY_CAT_COUNT) return 0; + if (!n->title) return 0; + + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + + /* 未声明类型:自动注册(≤4 种,超过则拒绝本次发送) */ + if (!notify_is_declared(n->cat)) { + if (g_cat_n >= MEOW_NOTIFY_MAX_TYPES) { + pthread_mutex_unlock(&g_mu); + return 0; + } + g_cats[g_cat_n++] = (int)n->cat; + } + + slot_idx = -1; + if (n->cat == MEOW_NOTIFY_CAT_MEDIA) { + /* 媒体单例:复用已存在的媒体槽(重发替换同 app 上一条媒体通知) */ + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { slot_idx = i; break; } + } else { + count = notify_count_cat(n->cat); + if (count >= MEOW_NOTIFY_MAX_PER_TYPE) { + /* 满则丢该类型最旧一条(替换) */ + oldest_idx = -1; + oldest = UINT64_MAX; + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active && g_slots[i].cat == n->cat && g_slots[i].created_ms < oldest) { + oldest = g_slots[i].created_ms; + oldest_idx = i; + } + if (oldest_idx >= 0) { g_slots[oldest_idx].active = 0; g_slots[oldest_idx].id = 0; } + } + } + if (slot_idx < 0) { + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (!g_slots[i].active) { slot_idx = i; break; } + } + if (slot_idx < 0) { + /* 理论不可达(上方已丢最旧腾位);兜底防御 */ + pthread_mutex_unlock(&g_mu); + return 0; + } + + now = meow_ui_time_ms(); + id = g_next_id++; + if (g_next_id == 0) g_next_id = 1; /* 防回绕 */ + notify_fill(&g_slots[slot_idx], n, id, now); + notify_deliver(&g_slots[slot_idx], "send"); + pthread_mutex_unlock(&g_mu); + return id; +} + +int meow_notify_cancel(uint32_t id) +{ + int i, rc = 0; + if (id == 0) return 0; + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) { + if (g_slots[i].active && g_slots[i].id == id) { + g_slots[i].active = 0; + g_slots[i].id = 0; + rc = 1; + break; + } + } + pthread_mutex_unlock(&g_mu); + return rc; +} + +int meow_notify_active_count(void) +{ + int i, n = 0; + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active) n++; + pthread_mutex_unlock(&g_mu); + return n; +} + +int meow_notify_count_in_type(meow_notify_category cat) +{ + int n; + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + n = notify_count_cat(cat); + pthread_mutex_unlock(&g_mu); + return n; +} + +int meow_notify_has_active(void) +{ + int i, rc = 0; + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active) { rc = 1; break; } + pthread_mutex_unlock(&g_mu); + return rc; +} + +const char *meow_notify_urgency_str(meow_notify_urgency u) +{ + /* 钳位(同 meow_ui_log.c 防越界枚举触发 OOB 读) */ + if ((int)u >= 0 && (int)u < (int)MEOW_NOTIFY_URG_COUNT) return g_urg_str[u]; + return g_urg_str[MEOW_NOTIFY_URG_NORMAL]; +} + +const char *meow_notify_category_str(meow_notify_category c) +{ + if ((int)c >= 0 && (int)c < (int)MEOW_NOTIFY_CAT_COUNT) return g_cat_str[c]; + return g_cat_str[MEOW_NOTIFY_CAT_GENERAL]; +} diff --git a/meow_ui_sys.c b/meow_ui_sys.c new file mode 100644 index 0000000..07c5ef8 --- /dev/null +++ b/meow_ui_sys.c @@ -0,0 +1,231 @@ +/* + * meow_ui_sys.c — SDK 内置系统状态读取器 + 全局状态栏(A-0 核心)。 + * + * 职责: + * 1. 状态读取器:读 /sys/class/power_supply/(电量/充电)、WiFi operstate、 + * 蓝牙实上电、音量(/usr/data/meow/settings.conf 的 audio.volume,与 launcher + * 同源单点);时钟在绘制时现取 localtime。1s 轻刷,蓝牙 30s 缓存。 + * 2. 全局状态栏绘制:把时钟/电量/WiFi/蓝牙/音量画进 back_buf 顶部 MUI_SBAR_H 行; + * 由渲染后端 present() 前置调用(z 最顶),非全屏可见。 + * 3. 显隐/全屏状态 + 公共 API(meow_ui_set_statusbar / set_fullscreen / ...)。 + * + * 注意:所有读取在事件循环主线内做(无独立线程),1s 频率同步读取 sysfs/小文件, + * 开销可忽略;蓝牙 hciconfig 为 popen(spawn) 较贵,仅 30s 刷一次并缓存。 + */ +#include "meow_ui_sys.h" +#include "meow_ui_internal.h" + +#include +#include +#include +#include +#include +#include + +#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" +#define VOL_DEFAULT 60 + +/* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */ +#define BT_REFRESH_MS 30000 + +static meow_sys_state g_sys; +static int g_sys_inited = 0; + +/* 显隐/全屏状态(公共 API 与绘制/输入共用) */ +static int g_statusbar_on = 1; /* 默认显示 */ +static int g_fullscreen = 0; /* 默认非全屏 */ +static int g_statusbar_force = 0; /* 1s 刷新后置位,强制重画顶部条 */ + +static uint64_t now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; +} + +/* 读一份 sysfs/文件字符串(剥尾随换行);失败回退 "" */ +static void read_str(const char *path, char *buf, size_t n) +{ + buf[0] = 0; + int fd = open(path, O_RDONLY); + if (fd < 0) return; + ssize_t r = read(fd, buf, n - 1); + close(fd); + if (r <= 0) { buf[0] = 0; return; } + buf[r] = 0; + while (r > 0 && (buf[r-1] == '\n' || buf[r-1] == '\r')) buf[--r] = 0; +} + +static int read_int(const char *path) +{ + char buf[24]; + read_str(path, buf, sizeof buf); + if (!buf[0]) return -1; + return atoi(buf); +} + +static int hci_powered(void) +{ + FILE *p = popen("hciconfig -a hci0 2>/dev/null", "r"); + if (!p) return 0; + int up = 0; + char buf[256]; + while (fgets(buf, sizeof buf, p)) { + char *s = buf, *tok; + while ((tok = strsep(&s, " \t\r\n")) != NULL) { + if (!strcmp(tok, "UP")) { up = 1; break; } + } + } + pclose(p); + return up; +} + +/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 */ +static int settings_vol(void) +{ + FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); + if (!f) return VOL_DEFAULT; + int vol = VOL_DEFAULT; + char buf[128]; + while (fgets(buf, sizeof buf, f)) { + if (!strncmp(buf, "audio.volume=", 13)) { + int v = atoi(buf + 13); + if (v >= 0 && v <= 100) vol = v; + break; + } + } + fclose(f); + return vol; +} + +int meow_ui_sys_init(void) +{ + memset(&g_sys, 0, sizeof g_sys); + g_sys.batt = -1; + g_sys.vol = settings_vol(); + g_statusbar_on = 1; + g_fullscreen = 0; + g_statusbar_force = 1; + meow_ui_sys_refresh(); + g_sys_inited = 1; + return 0; +} + +void meow_ui_sys_refresh(void) +{ + g_sys.batt = read_int("/sys/class/power_supply/battery/capacity"); + if (g_sys.batt < 0 || g_sys.batt > 100) g_sys.batt = -1; + char st[16]; + read_str("/sys/class/power_supply/battery/status", st, sizeof st); + g_sys.charging = (strstr(st, "Charg") || strstr(st, "Full")) ? 1 : 0; + + char op[16]; + read_str("/sys/class/net/wlan0/operstate", op, sizeof op); + g_sys.wifi_on = !strcmp(op, "up") ? 1 : 0; + + g_sys.vol = settings_vol(); + + /* 蓝牙:hci0 存在性检查便宜;上电状态贵(hciconfig spawn),30s 缓存。 */ + static uint64_t last_bt_ms = 0; + uint64_t n = now_ms(); + g_sys.bt_seen = access("/sys/class/bluetooth/hci0", F_OK) == 0; + if (!g_sys.bt_seen) { + g_sys.bt_on = 0; + last_bt_ms = n; + } else if (!last_bt_ms || (n - last_bt_ms) >= BT_REFRESH_MS) { + g_sys.bt_on = hci_powered(); + last_bt_ms = n; + } + + /* 时钟每秒变 → 强制顶条重画(时间在绘制时现取) */ + g_statusbar_force = 1; +} + +const meow_sys_state *meow_ui_sys_get(void) { return &g_sys; } + +/* ---- 公共 API ---- */ +void meow_ui_set_statusbar(int visible) +{ + g_statusbar_on = visible ? 1 : 0; + g_statusbar_force = 1; /* 显隐切换也要重画顶条 */ +} + +int meow_ui_statusbar_visible(void) +{ + return g_statusbar_on && !g_fullscreen; +} + +void meow_ui_set_fullscreen(int on) +{ + g_fullscreen = on ? 1 : 0; + g_statusbar_force = 1; /* 进出全屏立即反映(显示/隐藏顶条) */ +} + +int meow_ui_statusbar_height(void) +{ + return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; +} + +/* ---- 内部(gfx/input 用) ---- */ +int meow_ui_sys_statusbar_active(void) +{ + return g_statusbar_on && !g_fullscreen; +} + +int meow_ui_sys_statusbar_height(void) +{ + return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; +} + +int meow_ui_sys_statusbar_force(void) +{ + int f = g_statusbar_force; + g_statusbar_force = 0; + return f; +} + +/* 状态栏右侧信息串:batt% WIFI 蓝牙/蓝牙 VOL nn%(同 launcher statusbar_right_str) */ +static void statusbar_right_str(char *out, size_t n) +{ + char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = ""; + if (g_sys.batt >= 0) snprintf(batt, sizeof batt, "%d%%", g_sys.batt); + if (g_sys.wifi_on) snprintf(wifi, sizeof wifi, "WIFI"); + snprintf(vol, sizeof vol, "VOL %d%%", g_sys.vol); + if (g_sys.bt_seen && g_sys.bt_on) + snprintf(bt, sizeof bt, meow_gfx_mfont_ready() ? "蓝牙" : "BT"); + out[0] = 0; + if (batt[0]) strcat(out, batt); + if (wifi[0]) { if (out[0]) strcat(out, " "); strcat(out, wifi); } + if (bt[0]) { if (out[0]) strcat(out, " "); strcat(out, bt); } + if (vol[0]) { if (out[0]) strcat(out, " "); strcat(out, vol); } +} + +void meow_ui_sys_statusbar_render(void) +{ + if (!meow_ui_sys_statusbar_active()) return; + const meow_theme *th = meow_theme_get(); + int w = meow_ui_width(); + int h = MUI_SBAR_H; + + /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */ + meow_ui_fill_rect(0, 0, w, h, th->bg); + + /* 时钟(左):每秒现取 localtime */ + char timebuf[16]; + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); + meow_ui_text(8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, timebuf, MUI_SBAR_FONT, th->fg); + + /* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */ + char right[96]; + statusbar_right_str(right, sizeof right); + if (right[0]) + meow_ui_text_right(w - 8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, right, MUI_SBAR_FONT, th->fg); + + /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=40 从 y=40 起即干净 */ + meow_ui_fill_rect(0, h - 1, w, 1, th->dim); + + g_statusbar_force = 0; +} diff --git a/meow_ui_sys.h b/meow_ui_sys.h new file mode 100644 index 0000000..61561b3 --- /dev/null +++ b/meow_ui_sys.h @@ -0,0 +1,57 @@ +/* + * meow_ui_sys.h — SDK 内置系统状态读取器 + 全局状态栏(A-0 核心)。 + * + * 供 meow_ui 内部各 TU 协作(绘制后端 present() 前置覆盖 + 输入层事件循环 1s 轻刷)。 + * 不随 meow_ui.h 发布给应用。应用侧只通过公共 API 控制显隐/全屏: + * meow_ui_set_statusbar() / meow_ui_statusbar_visible() / + * meow_ui_set_fullscreen() / meow_ui_statusbar_height()。 + * + * 数据源与 launcher 的 meow_sys 同源单点一致: + * - 电量 /sys/class/power_supply/battery/{capacity,status} + * - WiFi /sys/class/net/wlan0/operstate + * - 蓝牙 实际 powered(hciconfig -a hci0 的 UP;spawn 贵 → 缓存 30s 刷一次) + * - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件) + * - 时钟 localtime_r(每秒在绘制时现取) + */ +#ifndef MEOW_UI_SYS_H +#define MEOW_UI_SYS_H + +#include "meow_ui.h" + +/* 系统快照(状态栏数据源)。时钟不走这里——绘制时现取 localtime。 */ +typedef struct meow_sys_state { + int batt; /* 电量 %;-1=未知 */ + int charging; /* 1=充电/满 */ + int wifi_on; /* operstate==up */ + int bt_seen; /* hci0 节点存在 */ + int bt_on; /* 实际上电(hciconfig UP) */ + int vol; /* 音量 0-100 */ +} meow_sys_state; + +/* 顶部状态栏几何(与 launcher SB_BAR_H=40 / SB_FONT=28 对齐,视觉统一)。 */ +#define MUI_SBAR_H 40 +#define MUI_SBAR_FONT 28 + +/* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */ +int meow_ui_sys_init(void); + +/* 1s 轻刷:电量/WiFi/音量(便宜);蓝牙 30s 缓存刷新。 */ +void meow_ui_sys_refresh(void); + +/* 当前快照(内部只读)。 */ +const meow_sys_state *meow_ui_sys_get(void); + +/* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */ +int meow_ui_sys_statusbar_active(void); + +/* 状态栏高度(px):active 时 40,否则 0(供 meow_ui_statusbar_height 用)。 */ +int meow_ui_sys_statusbar_height(void); + +/* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 + * 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */ +int meow_ui_sys_statusbar_force(void); + +/* 把状态栏画进 back_buf(顶部 MUI_SBAR_H 行)+ 标脏。调用方负责随后上屏。 */ +void meow_ui_sys_statusbar_render(void); + +#endif /* MEOW_UI_SYS_H */ diff --git a/meow_ui_theme.c b/meow_ui_theme.c new file mode 100644 index 0000000..305ad9a --- /dev/null +++ b/meow_ui_theme.c @@ -0,0 +1,197 @@ +/* + * meow_ui_theme.c — 主题/配置(B1.5 / M2.46)。 + * + * 内置日间/夜间两套色板;模式跟随系统设置键 display.theme_mode + display.theme + * (/usr/data/meow/settings.conf,与 launcher meow_settings 同源)。 + * 判定与 launcher 的 s_ui_theme() 完全对齐(避免两套逻辑漂移): + * - display.theme_mode = manual:直读 display.theme(dark/light)决定深浅。 + * - display.theme_mode = auto:按深色时段 display.theme_dark_start/end(分钟 0-1439), + * 半开区间 [start,end);start>end 跨午夜;start==end 视为未配置 → 回退 manual(display.theme)。 + * 读取缺失/非法一律回退深色(与 launcher 默认一致)。 + * 切换即广播 listener。AUTO 解析只在查询时按当前系统时间计算(不缓存)。 + */ +#include "meow_ui.h" +#include +#include +#include +#include + +#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" + +static meow_theme g_light = { + 0xE8EAF0, 0x1A1A2A, 0x2A5A8A, 0x4A4F5A, 28, 20 +}; +static meow_theme g_dark = { + 0x141824, 0xFFFFFF, 0x4A6A9A, 0x9AA4C8, 28, 20 +}; + +static meow_theme_mode g_mode = MEOW_THEME_DARK; /* 配置生效的判定模式(默认深色,与 launcher 默认一致) */ +static meow_theme_mode g_manual = MEOW_THEME_DARK; /* display.theme 解析出的手动深浅;auto 未配置时段时回退用之 */ +static int g_dark_start = 0; /* 深色时段开始(分钟 0-1439) */ +static int g_dark_end = 0; /* 深色时段结束(分钟 0-1439) */ +static int g_loaded = 0; + +static meow_theme_listener g_listeners[8]; +static int g_nlisteners = 0; + +/* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)---- + * 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白, + * 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */ +static int get_setting_str(const char *key, char *buf, int buflen) +{ + FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); + if (!f || buflen <= 0) return -1; + char line[160]; + int kl = (int)strlen(key); + int found = 0; + while (fgets(line, sizeof line, f)) { + char *eq = strchr(line, '='); + if (!eq) continue; + /* key = 行首到 '=',去尾空白 */ + char *k = line; + while (*k == ' ' || *k == '\t') k++; + int n = (int)(eq - k); + while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--; + if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; + /* value = '=' 后,去首尾空白 / 行尾 CRLF */ + const char *v = eq + 1; + while (*v == ' ' || *v == '\t') v++; + int m = 0; + while (*v && *v != '\n' && *v != '\r' && m < buflen - 1) buf[m++] = *v++; + while (m > 0 && (buf[m-1] == ' ' || buf[m-1] == '\t')) m--; + buf[m] = 0; + found = 1; + break; + } + fclose(f); + return found ? 0 : -1; +} + +static int get_setting_int(const char *key, int *out) +{ + char buf[32]; + if (get_setting_str(key, buf, sizeof buf) != 0) return -1; + *out = atoi(buf); + return 0; +} + +/* 解析 display.theme 的值(dark/light,非索引)→ LIGHT/DARK;未知/缺失回退 DARK(launcher 默认)。 */ +static meow_theme_mode parse_manual(const char *v) +{ + while (*v == ' ' || *v == '\t') v++; + if (!strncmp(v, "light", 5)) return MEOW_THEME_LIGHT; + return MEOW_THEME_DARK; /* "dark" / 未知 → 深色 */ +} + +static void load_settings(void) +{ + char buf[64]; + + g_manual = MEOW_THEME_DARK; + g_mode = MEOW_THEME_DARK; + g_dark_start = 0; + g_dark_end = 0; + + /* display.theme:手动深浅(auto 未配置时段时也回退这里) */ + if (get_setting_str("display.theme", buf, sizeof buf) == 0) + g_manual = parse_manual(buf); + + /* display.theme_mode:manual=读 display.theme;auto=时段。缺失/非法 → manual */ + if (get_setting_str("display.theme_mode", buf, sizeof buf) == 0) { + if (!strcmp(buf, "auto")) g_mode = MEOW_THEME_AUTO; + else g_mode = g_manual; /* manual(含 "light"/"dark"/未知)→ 读 display.theme */ + } else { + g_mode = g_manual; /* 无 theme_mode 键 → 默认 manual → display.theme */ + } + + get_setting_int("display.theme_dark_start", &g_dark_start); + get_setting_int("display.theme_dark_end", &g_dark_end); +} + +/* 当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用(与 launcher s_now_min_of_day 一致)。 */ +static int now_min_of_day(void) +{ + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + return tm.tm_hour * 60 + tm.tm_min; +} + +/* 深色时段判定(与 launcher theme_should_dark 完全一致):now_min ∈ [start,end) → 深色。 + * - start==end 视为未配置,返回 -1(由调用方决定回退策略)。 + * - startend:跨午夜(如 1140→420 = 19:00→次日07:00)。 + * - 边界:end 半开(恰好 end 分回到浅色),start 闭(恰好 start 分进入深色)。 */ +static int theme_should_dark(int now_min, int start, int end) +{ + if (start == end) return -1; /* 未配置 */ + if (start < end) + return (now_min >= start && now_min < end) ? 1 : 0; + return (now_min >= start || now_min < end) ? 1 : 0; /* 跨午夜 */ +} + +static meow_theme_mode resolve_mode(meow_theme_mode m) +{ + if (m != MEOW_THEME_AUTO) return m; + int d = theme_should_dark(now_min_of_day(), g_dark_start, g_dark_end); + if (d < 0) return g_manual; /* 时段未配置:回退 display.theme 手动深浅 */ + return d ? MEOW_THEME_DARK : MEOW_THEME_LIGHT; +} + +static void broadcast(void) +{ + for (int i = 0; i < g_nlisteners; i++) + if (g_listeners[i]) g_listeners[i](g_mode); +} + +const meow_theme *meow_theme_get(void) +{ + if (!g_loaded) { + load_settings(); + g_loaded = 1; + } + return resolve_mode(g_mode) == MEOW_THEME_DARK ? &g_dark : &g_light; +} + +meow_theme_mode meow_theme_get_mode(void) +{ + if (!g_loaded) { load_settings(); g_loaded = 1; } + return g_mode; +} + +void meow_theme_set_mode(meow_theme_mode m) +{ + if (!g_loaded) { load_settings(); g_loaded = 1; } + if (m < MEOW_THEME_LIGHT || m > MEOW_THEME_AUTO) return; + if (m == g_mode) return; + g_mode = m; + broadcast(); +} + +meow_theme meow_theme_defaults(meow_theme_mode m) +{ + switch (resolve_mode(m)) { + case MEOW_THEME_DARK: return g_dark; + case MEOW_THEME_LIGHT: + default: return g_light; + } +} + +void meow_theme_add_listener(meow_theme_listener cb) +{ + if (!cb) return; + if (g_nlisteners >= (int)(sizeof g_listeners / sizeof g_listeners[0])) return; + for (int i = 0; i < g_nlisteners; i++) if (g_listeners[i] == cb) return; + g_listeners[g_nlisteners++] = cb; +} + +void meow_theme_remove_listener(meow_theme_listener cb) +{ + for (int i = 0; i < g_nlisteners; i++) { + if (g_listeners[i] == cb) { + g_listeners[i] = g_listeners[g_nlisteners - 1]; + g_nlisteners--; + return; + } + } +} diff --git a/meow_ui_widget.c b/meow_ui_widget.c new file mode 100644 index 0000000..e7d0cac --- /dev/null +++ b/meow_ui_widget.c @@ -0,0 +1,135 @@ +/* + * meow_ui_widget.c — 控件层(B1.3):按钮/滑条/开关/列表最小集。 + * + * 控件只负责绘制与状态;命中分发由应用在 on_tap 里调 meow_widget_hit 完成 + * (与 MEOW_APP_SDK.md §5 一致)。绘制读当前主题(meow_theme_get)走 back_buf。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include + +enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; + +struct meow_widget { + int type; + int x, y, w, h; + char label[128]; + int pressed; /* 按钮按下视觉态 */ + int min, max, val; /* 滑条 */ + int on; /* 开关 */ + int count, sel; /* 列表 */ + meow_list_source src; + void *ctx; +}; + +meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h; + if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + return t; +} + +meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40; + t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; + return t; +} + +meow_widget *meow_switch_create(int x, int y, int on) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32; + t->on = on ? 1 : 0; + return t; +} + +meow_widget *meow_list_create(int x, int y, int w, int h, int count, + meow_list_source src, void *ctx) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h; + t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; + return t; +} + +void meow_widget_free(meow_widget *w) { if (w) free(w); } + +static int hit(meow_widget *w, int x, int y) +{ + if (!w) return 0; + return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h; +} + +int meow_widget_hit(meow_widget *w, int x, int y) { return hit(w, x, y); } +int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; } + +void meow_slider_set(meow_widget *w, int val) +{ + if (!w || w->type != W_SLIDER) return; + if (val < w->min) val = w->min; if (val > w->max) val = w->max; + w->val = val; +} +int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; } +void meow_switch_set(meow_widget *w, int on) +{ if (w && w->type == W_SWITCH) w->on = on ? 1 : 0; } +int meow_switch_get(meow_widget *w) { return w && w->type == W_SWITCH ? w->on : 0; } + +/* 供应用在 on_tap 里对外可控回馈:这里不存命中态(由 app 分发),仅暴露绘制 */ +void meow_widget_draw(meow_widget *w) +{ + if (!w) return; + const meow_theme *th = meow_theme_get(); + switch (w->type) { + case W_BTN: { + uint32_t bg = w->pressed ? th->dim : th->accent; + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); + meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, + w->label, th->font_body, 0xFFFFFF); + break; + } + case W_SLIDER: { + int trace_y = w->y + w->h / 2 - 3; + meow_ui_fill_rect(w->x, trace_y, w->w, 6, th->dim); + int range = w->max - w->min; + int frac = range ? (w->val - w->min) * w->w / range : 0; + if (frac > 0) meow_ui_fill_rect(w->x, trace_y, frac, 6, th->accent); + int tx = w->x + frac; + int ty = w->y + w->h / 2; + meow_gfx_fill_round(tx - 10, ty - 10, 20, 20, 10, th->fg); + break; + } + case W_SWITCH: { + uint32_t bg = w->on ? th->accent : th->dim; + meow_gfx_fill_round(w->x, w->y, w->w, w->h, w->h / 2, bg); + int knob_r = w->h / 2 - 3; + int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3; + meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF); + break; + } + case W_LIST: { + int rowh = 44, pad = 16; + for (int i = 0; i < w->count; i++) { + int ry = w->y + i * rowh; + if (ry >= w->y + w->h) break; + if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->accent); + char label[64] = ""; + if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) { + uint32_t col = (i == w->sel) ? 0xFFFFFF : th->fg; + meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2, + label, th->font_body, col); + } + meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->dim); + } + break; + } + default: break; + } +} diff --git a/stb_image.c b/stb_image.c new file mode 100644 index 0000000..c9fa25b --- /dev/null +++ b/stb_image.c @@ -0,0 +1,13 @@ +/* + * stb_image.c — stb_image 实现 TU。仅解 PNG/JPEG/BMP(其余格式关闭以减体积)。 + * self-contained:PNG 内建 zlib 解码,不依赖系统 libz/libpng。 + */ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_PSD +#define STBI_NO_TGA +#define STBI_NO_GIF +#define STBI_NO_PIC +#define STBI_NO_PNM +#define STBI_NO_HDR +#define STBI_FAILURE_USERMSG +#include "stb_image.h" diff --git a/stb_image.h b/stb_image.h new file mode 100644 index 0000000..9eedabe --- /dev/null +++ b/stb_image.h @@ -0,0 +1,7988 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ From 318e6ec7c1a9b43acf59b0cbc9d24fd54795445b Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 03:27:24 +0800 Subject: [PATCH 02/49] =?UTF-8?q?WIP=20checkpoint=20(M2.53=E2=86=92M2.54?= =?UTF-8?q?=20=E5=B7=A5=E4=BD=9C=E5=8C=BA):=20=E5=AD=97=E4=BD=93=E5=BD=92M?= =?UTF-8?q?eowOS/fonts+SDK=E8=B7=9F=E9=9A=8F+=E5=A4=9A=E5=AD=97=E4=BD=93(?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=B8=AD)+boot=E6=9D=BF=E8=BD=BD+=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=8F28+=E4=BA=AE=E5=BA=A6/=E9=9F=B3=E9=87=8F?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D+=E6=96=87=E6=A1=A3=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build_sdk.sh | 6 +- meow_notify_ipc.c | 243 ++++++++++++++++++++++++++++++++ meow_notify_ipc.h | 44 ++++++ meow_ui.h | 2 +- meow_ui_gfx.c | 51 ++++++- meow_ui_input.c | 4 +- meow_ui_notify.c | 78 ++++++++--- meow_ui_sys.c | 33 ++--- meow_ui_sys.h | 12 +- meow_ui_vol.c | 52 +++++++ meow_ui_vol.h | 32 +++++ test_meow_ui_vol.c | 145 ++++++++++++++++++++ test_notify_integration.c | 202 +++++++++++++++++++++++++++ test_notify_ipc.c | 281 ++++++++++++++++++++++++++++++++++++++ test_notify_post.c | 150 ++++++++++++++++++++ 15 files changed, 1289 insertions(+), 46 deletions(-) create mode 100644 meow_notify_ipc.c create mode 100644 meow_notify_ipc.h create mode 100644 meow_ui_vol.c create mode 100644 meow_ui_vol.h create mode 100644 test_meow_ui_vol.c create mode 100644 test_notify_integration.c create mode 100644 test_notify_ipc.c create mode 100644 test_notify_post.c diff --git a/build_sdk.sh b/build_sdk.sh index 7d502e4..6589f06 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -9,10 +9,12 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys stb_image" +# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image" +CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" - "$CC" -Os -static -c "$f.c" -o "$f.o" + "$CC" $CFLAGS -c "$f.c" -o "$f.o" done # meow_font.o(复用 launcher 字库,打进 meow_ui.a → 应用只链一个库) diff --git a/meow_notify_ipc.c b/meow_notify_ipc.c new file mode 100644 index 0000000..a4c768b --- /dev/null +++ b/meow_notify_ipc.c @@ -0,0 +1,243 @@ +/* + * meow_notify_ipc.c — MeowOS SDK 通知 → launcher IPC 桥(任务 #5,SDK↔IPC 关键断点)。 + * + * 让 meow_notify_send() 的通知真正经 AF_UNIX IPC 送到 launcher 中央存储(meow_notif_post) + * 并回读 MSG_NOTIFY_ACK 拿 launcher 分配的通知 id(nid)。协议帧与负载结构复用 launcher 的 + * meow_ipc.h(同源一份、杜绝两端漂移): + * - 帧头 16B:magic=0x4D4F5701 / version=1 / type / req_id / len(全小端 LE,mipsel 原生); + * - MSG_NOTIFY(0x0020) ↑ 负载 = m_notify_t;MSG_NOTIFY_ACK(0x0021) ↓ 负载 = m_ack_t(status=nid); + * - MSG_NOTIFY_DISMISS(0x0022) ↑ 负载 = uint32 nid。 + * + * 设计要点: + * - 只做「一次性 connect + send + read ACK」,不维护连接/订阅循环(与 meow_ipc.c 客户端同型); + * - 映射层(SDK 枚举 ↔ IPC 字段)集中在本文件:分类枚举 CAT_* ↔ 字符串 + * "general"/"message"/"email"/"event"/"progress"/"media";timeout_ms → expire_timeout: + * >0 该毫秒、0=不自动过期、-1(ONGOING)=0xFFFFFFFF(并置 ongoing=1);媒体类型置 media=1; + * - 优雅降级:无 socket(connect 失败)/写失败/ACK 超时(SO_RCVTIMEO)/非 ACK 应答 → + * 返回 0(调用方 meow_ui_notify.c 保留本地 id 兜底),绝不崩; + * - 线程安全:app_id 经 pthread_once 缓存;socket 路径每次调用读 env MEOW_IPC_SOCK 覆盖 + * (宿主/test 用),缺省 meow_ipc.h 的 MEOW_IPC_PATH(/usr/data/meow_ipc.sock)。 + * + * 约定:与 meow_ui_notify.c 同风格(M2.x 中文注释);本文件为纯传输层,不调 meow_log, + * 失败诊断由调用方 meow_ui_notify.c 的 notify_deliver() 统一打点。 + */ +#include "meow_notify_ipc.h" +#include "meow_ipc.h" /* launcher 端共享协议:帧头/m_notify_t/m_ack_t/MSG_* —— 防漂移 */ + +#include /* snprintf */ +#include /* getenv */ +#include +#include /* EINTR */ +#include +#include +#include +#include +#include + +/* ACK 读超时(ms):launcher 主循环为 IPC 单写者(eventfd 唤醒,正常 <10ms 响应), + * 放宽容错 500ms;超时视为降级(返回 0,不崩)。 */ +#define MEOW_NOTIFY_IPC_TIMEOUT_MS 500 + +/* app_id(env MEOW_LOG_APP,缺省 "app";与 meow_ui_log.c 的取值口径一致)。 */ +static pthread_once_t g_once = PTHREAD_ONCE_INIT; +static char g_app_id[MEOW_APP_ID_MAX] = "app"; + +static void ipc_init(void) +{ + const char *s = getenv("MEOW_LOG_APP"); + if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s); +} + +/* socket 路径:优先 env MEOW_IPC_SOCK(宿主/qemu sim 覆盖),缺省 MEOW_IPC_PATH。 */ +static const char *ipc_sock_path(void) +{ + const char *s = getenv("MEOW_IPC_SOCK"); + return (s && s[0]) ? s : MEOW_IPC_PATH; +} + +const char *meow_notify_ipc_app_id(void) +{ + pthread_once(&g_once, ipc_init); + return g_app_id; +} + +/* ============================================================ */ +/* 映射层: SDK meow_notify 枚举/字段 ↔ IPC m_notify_t 字段 */ +/* ============================================================ */ + +/* SDK 分类枚举 → IPC 字符串(与 meow_ui.h §9.1 的 MEOW_NOTIFY_CAT_* 序号对齐; + * 越界钳位为 "general"——同 meow_notify_category_str 的防御语义)。 */ +static const char *cat_to_str(meow_notify_category cat) +{ + static const char *t[] = { "general", "message", "email", "event", "progress", "media" }; + if ((int)cat >= 0 && (int)cat < (int)MEOW_NOTIFY_CAT_COUNT) return t[cat]; + return t[MEOW_NOTIFY_CAT_GENERAL]; +} + +/* SDK timeout_ms → IPC expire_timeout(存储/显示端语义): + * >0 → 该毫秒数后过期;0 → 不自动过期(NEVER);<0 → 常驻(ONGOING)= 0xFFFFFFFF。 */ +static uint32_t timeout_to_expire(int timeout_ms) +{ + if (timeout_ms < 0) return 0xFFFFFFFFu; /* ONGOING:常驻,不自动过期 */ + return (uint32_t)timeout_ms; /* 0(NEVER)/ >0(毫秒) */ +} + +/* 构建 IPC 负载 m_notify_t(app_icon/actions/hints v1 留空,M2b D-Bus 桥再填)。 + * 仅在持有调用者互斥之外、发送前调用(纯 CPU 拷贝,不阻塞)。 */ +static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_t *mn) +{ + memset(mn, 0, sizeof *mn); + snprintf(mn->app_id, sizeof mn->app_id, "%s", meow_notify_ipc_app_id()); + snprintf(mn->category, sizeof mn->category, "%s", cat_to_str(n->cat)); + snprintf(mn->summary, sizeof mn->summary, "%s", n->title ? n->title : ""); + snprintf(mn->body, sizeof mn->body, "%s", n->body ? n->body : ""); + snprintf(mn->target_activity, sizeof mn->target_activity, "%s", + n->target_activity ? n->target_activity : ""); + mn->replaces_id = replaces_id; /* 0=新建;>0=更新(不存在则落为新建,见 launcher) */ + mn->expire_timeout = timeout_to_expire(n->timeout_ms); + mn->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); + mn->media = (n->cat == MEOW_NOTIFY_CAT_MEDIA) ? 1 : 0; + mn->ongoing = (n->timeout_ms == MEOW_NOTIFY_TIMEOUT_ONGOING) ? 1 : 0; +} + +/* ============================================================ */ +/* 传输层(AF_UNIX SOCK_STREAM;一次性 connect + send + read) */ +/* ============================================================ */ + +/* 连接 socket。失败返回 -1(不崩;调用方降级)。 */ +static int conn_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { + close(fd); + return -1; + } + return fd; +} + +/* 写满 n 字节(EINTR 重试);任一次写失败返回 0。 */ +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return 0; } + if (w == 0) return 0; + off += (size_t)w; + } + return 1; +} + +/* 读满 n 字节(超时/断开返回 0)。 */ +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return 0; } + if (r == 0) return 0; + off += (size_t)r; + } + return 1; +} + +/* 组装并写出 16B(LE)帧头 + 负载。 */ +static int send_frame(int fd, uint16_t type, uint32_t req_id, + const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC; + uint16_t ver = MEOW_IPC_VERSION; + memcpy(hdr + 0, &magic, 4); + memcpy(hdr + 4, &ver, 2); + memcpy(hdr + 6, &type, 2); + memcpy(hdr + 8, &req_id, 4); + memcpy(hdr + 12, &len, 4); + if (!write_all(fd, hdr, sizeof hdr)) return 0; + if (len && payload && !write_all(fd, payload, len)) return 0; + return 1; +} + +/* 给 conn 设 ACK 读超时(失败忽略——超时降级由 read_all 兜底)。 */ +static void set_ack_timeout(int fd) +{ + struct timeval tv; + tv.tv_sec = MEOW_NOTIFY_IPC_TIMEOUT_MS / 1000; + tv.tv_usec = (MEOW_NOTIFY_IPC_TIMEOUT_MS % 1000) * 1000; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); +} + +uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id) +{ + m_notify_t mn; + int fd; + if (!n) return 0; + build_m_notify(n, replaces_id, &mn); + + fd = conn_path(ipc_sock_path()); + if (fd < 0) return 0; /* 无 socket:优雅降级 */ + set_ack_timeout(fd); + + if (!send_frame(fd, MSG_NOTIFY, 0x4E4F5449u /* "NOTI" */, &mn, (uint32_t)sizeof mn)) { + close(fd); /* 写失败:降级 */ + return 0; + } + + /* 读回 16B 应答头 + m_ack_t,取 launcher 分配的通知 id(a.status)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t nid = 0; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint32_t rmagic, rlen; + uint16_t rver, rtype; + memcpy(&rmagic, rhdr + 0, 4); + memcpy(&rver, rhdr + 4, 2); + memcpy(&rtype, rhdr + 6, 2); + memcpy(&rlen, rhdr + 12, 4); + if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && + rtype == MSG_NOTIFY_ACK && rlen >= sizeof(m_ack_t)) { + uint8_t pay[sizeof(m_ack_t)]; + if (read_all(fd, pay, sizeof(m_ack_t))) { + m_ack_t a; + memcpy(&a, pay, sizeof a); + if (a.code == MEOW_OK) nid = a.status; /* 落库成功 → nid */ + } + } + } + close(fd); + return nid; /* 非 ACK/超时 → 0(降级) */ +} + +void meow_notify_ipc_dismiss(uint32_t nid) +{ + int fd; + if (nid == 0) return; + fd = conn_path(ipc_sock_path()); + if (fd < 0) return; /* 无 socket:静默 */ + set_ack_timeout(fd); + if (!send_frame(fd, MSG_NOTIFY_DISMISS, 0x4E4F5444u /* "NOTD" */, &nid, (uint32_t)sizeof nid)) { + close(fd); /* 写失败:静默 */ + return; + } + /* 读回应答(launcher 对 DISMISS 回 MSG_ACK,见 meow_ipc.c cmd_dismiss)。虽为 + * best-effort,但读回确保 launcher 已处理完撤下再关连接(避免写后即关的丢帧竞态)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint32_t rmagic, rlen; + uint16_t rver, rtype; + memcpy(&rmagic, rhdr + 0, 4); + memcpy(&rver, rhdr + 4, 2); + memcpy(&rtype, rhdr + 6, 2); + memcpy(&rlen, rhdr + 12, 4); + if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && + rtype == MSG_ACK && rlen >= sizeof(m_ack_t)) { + uint8_t pay[sizeof(m_ack_t)]; + (void)read_all(fd, pay, sizeof(m_ack_t)); /* 读到即确认;内容不关心 */ + } + } + close(fd); +} diff --git a/meow_notify_ipc.h b/meow_notify_ipc.h new file mode 100644 index 0000000..c1da243 --- /dev/null +++ b/meow_notify_ipc.h @@ -0,0 +1,44 @@ +/* + * meow_notify_ipc.h — MeowOS SDK 通知 → launcher IPC 桥(内部头,不随 meow_ui.h 发布给应用)。 + * + * 定位(任务 #5 SDK↔IPC 桥关键断点):让框架 app 调 meow_notify_send() 的通知真正经 + * AF_UNIX IPC 送到 launcher(meow_launcherd)中央存储(meow_notif.c 的 meow_notif_post), + * 通知中心(阶段 B · M5)才能显示。消息通道(FRAMEWORK_DESIGN §1 / meow_ipc.h): + * - MSG_NOTIFY 0x0020 ↑:推送/更新通知(负载 m_notify_t); + * - MSG_NOTIFY_ACK 0x0021 ↓:落库结果(m_ack_t,status=launcher 分配的通知 id nid); + * - MSG_NOTIFY_DISMISS 0x0022 ↑:撤下通知(负载 uint32 nid)。 + * + * 约定:只做「一次性发送 + 读 ACK」,不造 IPC 订阅循环;SDK 本地活动表(meow_ui_notify.c) + * 保留(app 进程内即时查询/配额自检),真正的通知中心数据在 launcher 中央存储,二者以 + * 本接口经 IPC 同步。全部失败路径优雅降级(无 socket/写失败/超时 → 返回 0,不崩)。 + * + * 本接口只暴露「SDK 类型出/进 + 状态码」,不把 m_notify_t / 帧格式泄漏给 meow_ui_notify.c; + * 映射层(SDK 枚举 ↔ IPC 字符串/uint32)实现在 meow_notify_ipc.c 内部。 + */ +#ifndef MEOW_NOTIFY_IPC_H +#define MEOW_NOTIFY_IPC_H + +#include +#include "meow_ui.h" /* meow_notify */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */ +const char *meow_notify_ipc_app_id(void); + +/* 把一条 SDK meow_notify 映射为 m_notify_t 并一次性发送 MSG_NOTIFY,阻塞读 + * MSG_NOTIFY_ACK 取 launcher 分配的通知 id(nid)。replaces_id:0=新建;>0=更新已存在 + * 通知(与 freedesktop 语义一致)。成功返回 nid(>0);无 socket/写失败/超时/非 ACK → + * 返回 0(调用方保留本地 id 兜底,不崩)。 */ +uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id); + +/* 一次性发送 MSG_NOTIFY_DISMISS 撤下 launcher 侧通知。best-effort:写失败/无 socket + * 静默(不崩、不回读 ACK)。 */ +void meow_notify_ipc_dismiss(uint32_t nid); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_NOTIFY_IPC_H */ diff --git a/meow_ui.h b/meow_ui.h index b4f0552..bca5b25 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -32,7 +32,7 @@ extern "C" { #define MEOW_KEY_VOLUMEDOWN 114 #define MEOW_KEY_VOLUMEUP 115 #define MEOW_KEY_PLAYPAUSE 164 -#define MEOW_KEY_PREVIOUS 19 /* KEY_PREVIOUSSONG */ +#define MEOW_KEY_PREVIOUS 165 /* KEY_PREVIOUSSONG=165(上一曲;19 是另一码,勿混用) */ #define MEOW_KEY_NEXT 163 /* KEY_NEXTSONG */ /* 滑动方向(on_swipe 的 dir 参数) */ diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index 6397fef..83a6abe 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -29,10 +29,14 @@ #define FBIO_WAITFORVSYNC 0x4620 #define FB_DEV "/dev/fb0" -#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.png" -#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowAPPs/fonts/font.idx" +#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png" +#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx" #define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" +/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」 + * 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): + * MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = 或 "default"。 + * SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */ #define MUI_DEFAULT_W 480 #define MUI_DEFAULT_H 720 @@ -572,13 +576,55 @@ int meow_gfx_is_device(void) { return g_fb_mem != NULL; } int meow_gfx_mfont_ready(void) { return g_mfont_ready; } /* ================= 初始化/销毁 ================= */ +/* 字库加载优先级(M2.x): + * 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file) + * —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file; + * 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts); + * 3) 固件 /usr/share/meowos/font/font.png。 + * 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */ static int load_font(void) { + 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; if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1; if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1; return 0; } +/* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、 + * ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c + * launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类 + * MEOW_FONT_CLS__PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。 + * 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */ +static void meow_gfx_load_multifont(void) +{ + static const char *cls_env[MEOW_FCLS_COUNT][2] = { + { "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_IDX" }, + { "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_CJK_SYM_IDX" }, + { "MEOW_FONT_CLS_NUM_PNG", "MEOW_FONT_CLS_NUM_IDX" }, + { "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_IDX" }, + { "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_LATIN_SYM_IDX" }, + { "MEOW_FONT_CLS_OTHER_PNG", "MEOW_FONT_CLS_OTHER_IDX" }, + }; + const char *emf = getenv("MEOW_MULTI_FONT"); + int on = emf && emf[0] == '1'; + const char *gp = getenv("MEOW_FONT_PNG"); + const char *gi = getenv("MEOW_FONT_IDX"); + meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; + memset(cfg, 0, sizeof cfg); + for (int c = 0; c < MEOW_FCLS_COUNT; c++) { + const char *p = getenv(cls_env[c][0]); + const char *idx = getenv(cls_env[c][1]); + if (p && p[0] && idx && idx[0]) { cfg[c].png = p; cfg[c].idx = idx; } + } + int distinct = 0, truncated = 0; + if (meow_font_config_multifont(on, gp, gi, cfg, &distinct, &truncated) != 0) + meow_log(MEOW_LOG_WARN, "multi_font: config failed, single-font fallback"); + else if (truncated) + meow_log(MEOW_LOG_WARN, "multi_font: >%d distinct fonts -> capped (more memory!)", distinct); +} + int meow_ui_init(const meow_ui_opts *opts) { if (g_init_ok) return 0; @@ -623,6 +669,7 @@ int meow_ui_init(const meow_ui_opts *opts) meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line); g_mfont_ready = load_font(); + if (g_mfont_ready) meow_gfx_load_multifont(); /* M2.5x 多字体(读 env;关/无 env = 单字体) */ const char *dp = getenv("MEOW_UI_DUMP"); if (dp && *dp) g_dump_path = strdup(dp); diff --git a/meow_ui_input.c b/meow_ui_input.c index aa55289..fed0fc6 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -219,9 +219,9 @@ static void *input_thread(void *arg) else if (!val && t_down) touch_release(); } else if (code == MEOW_KEY_POWER || code == MEOW_KEY_VOLUMEDOWN || code == MEOW_KEY_VOLUMEUP || code == MEOW_KEY_PLAYPAUSE || - code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 165) { + code == MEOW_KEY_PREVIOUS || code == MEOW_KEY_NEXT || code == 19) { /* M2.22(A)/M2.24 纪律:物理键(电源 116 / 音量 114,115 / 播放 164 / - * 上一曲 19,165 / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费, + * 上一曲 165(MEOW_KEY_PREVIOUS)/19(旧码) / 下一曲 163)全部是系统语义 —— watchdog/launcher 统一消费, * 绝不下发 APP。应用不碰物理键、不把任何物理键映射成确认/exit/返回/翻页 * (KEY_CONTROL_DESIGN §1.3 / APPS.md §3.4.1);APP 只接收 tap/swipe/tick。 * 忽略该键,不发 MEOW_EV_KEY。 */ diff --git a/meow_ui_notify.c b/meow_ui_notify.c index 2a02957..dbad728 100644 --- a/meow_ui_notify.c +++ b/meow_ui_notify.c @@ -12,15 +12,20 @@ * (替换);媒体类型单例(每 app 仅保留最近 1 条,重发替换——可多媒体 App)。 * - 超时语义 [实现定案]:timeout_ms > 0 为该毫秒数后过期;0 = 不自动过期(直到 * meow_notify_cancel);-1 = 常驻(ONGOING),同样不自动过期。 - * - 落点:本阶段内部维护活动通知表 + 用既有 meow_log() 打日志作为落点;真实投递到 - * 通知中心 UI / D-Bus 是后续任务——meow_notify 结构已对齐 freedesktop 字段 - * (summary/body/…)为此预留,但不写死后端。 + * - 落点(任务 #5 SDK↔IPC 桥):本表登记后,经 IPC 把 MSG_NOTIFY(0x0020) 送到 + * launcher 中央存储(meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021) + * 取 launcher 分配的通知 id(nid,存进 slot->ipc_id,供后续 replaces_id/dismiss 用)。 + * 通知中心 UI / D-Bus / TF 转储由 launcher 侧处理;SDK 本地活动表保留(app 进程内 + * 即时查询/配额自检)。无 socket/写失败/超时 → 优雅降级(保留本地 id 兜底,不崩)。 + * meow_notify 结构已对齐 freedesktop 字段(summary/body/…)为此预留。 * * 线程安全:pthread_mutex(同 meow_ui_log.c,launcher 前后台进程都会在已链 -lpthread - * 的应用里调用)。不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 - * meow_ui_time_ms()(单调毫秒)与 meow_log()(统一日志)两个既有 SDK 能力。 + * 的应用里调用),且 IPC 发送/读 ACK 在持锁之外进行(避免 socket I/O 阻塞其它查询)。 + * 不依赖 /dev/fb0、不依赖板子,可宿主编译。内部只复用 meow_ui_time_ms()(单调毫秒)、 + * meow_log()(统一日志)与 meow_notify_ipc_*(IPC 桥,纯传输层)三个既有/新增 SDK 能力。 */ #include "meow_ui.h" +#include "meow_notify_ipc.h" /* 任务 #5:通知 → launcher 中央存储的 IPC 桥(映射+传输) */ #include #include @@ -32,7 +37,8 @@ /* 活动通知槽(内部;标题/正文等复制副本,不持 app 外部指针,避免栈字符串悬垂) */ typedef struct { - uint32_t id; /* 唯一 id;0=空闲槽 */ + uint32_t id; /* 本地唯一 id(app 侧句柄);0=空闲槽 */ + uint32_t ipc_id; /* launcher 中央存储分配的通知 id(nid);0=未同步/降级 */ int active; /* 1=活动(未取消未过期),0=空闲 */ meow_notify_category cat; meow_notify_urgency urg; @@ -85,6 +91,7 @@ static void notify_purge_expired(void) if (s->timeout_ms > 0 && (now - s->created_ms) >= (uint64_t)s->timeout_ms) { s->active = 0; s->id = 0; + s->ipc_id = 0; } } } @@ -93,6 +100,7 @@ static void notify_purge_expired(void) static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uint64_t now) { s->id = id; + s->ipc_id = 0; /* 初值=未同步;发送后由 notify_deliver 回填 launcher 分配的 nid */ s->active = 1; s->cat = n->cat; s->urg = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT) @@ -106,12 +114,23 @@ static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uin s->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); } -/* 落点:本阶段用统一日志标出一条投递;真实后端(通知中心 UI / D-Bus)后续替换此处。 */ -static void notify_deliver(const notify_slot_t *s, const char *ev) +/* 落点(任务 #5):把通知经 IPC MSG_NOTIFY(0x0020) 送到 launcher 中央存储 + * (meow_notif.c 的 meow_notif_post),回读 MSG_NOTIFY_ACK(0x0021) 取 launcher 分配的 + * 通知 id(nid)。返回 nid(>0=成功);无 socket/写失败/超时 → 返回 0(调用方保留本地 id + * 兜底,不崩)。调用方须在持锁外调用(socket I/O 不占 g_mu,避免阻塞其它查询)。 + * replaces_id:0=新建;>0=更新 launcher 侧已存在通知(freedesktop 语义)。 */ +static uint32_t notify_deliver(const meow_notify *n, uint32_t replaces_id) { - meow_log(MEOW_LOG_INFO, "[notify] %s id=%u type=%s urgency=%s title=%s", - ev, (unsigned)s->id, meow_notify_category_str(s->cat), - meow_notify_urgency_str(s->urg), s->title); + uint32_t nid = meow_notify_ipc_send(n, replaces_id); + if (nid) { + meow_log(MEOW_LOG_INFO, "[notify] delivered nid=%u replaces=%u cat=%s title=%s", + (unsigned)nid, (unsigned)replaces_id, meow_notify_category_str(n->cat), + n->title ? n->title : ""); + } else { + meow_log(MEOW_LOG_INFO, "[notify] degraded (no launcher) cat=%s title=%s", + meow_notify_category_str(n->cat), n->title ? n->title : ""); + } + return nid; } /* ============================================================ */ @@ -132,7 +151,7 @@ int meow_notify_register_type(meow_notify_category cat) uint32_t meow_notify_send(const meow_notify *n) { - uint32_t id; + uint32_t id, nid, replaces_id = 0; uint64_t now, oldest; int i, slot_idx, count, oldest_idx; @@ -155,11 +174,16 @@ uint32_t meow_notify_send(const meow_notify *n) if (n->cat == MEOW_NOTIFY_CAT_MEDIA) { /* 媒体单例:复用已存在的媒体槽(重发替换同 app 上一条媒体通知) */ for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) - if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { slot_idx = i; break; } + if (g_slots[i].active && g_slots[i].cat == MEOW_NOTIFY_CAT_MEDIA) { + slot_idx = i; + replaces_id = g_slots[i].ipc_id; /* 更新该媒体通知(0=降级/未同步) */ + break; + } } else { count = notify_count_cat(n->cat); if (count >= MEOW_NOTIFY_MAX_PER_TYPE) { - /* 满则丢该类型最旧一条(替换) */ + /* 满则丢该类型最旧一条(更新):先取被丢最旧的 launcher nid 作 replaces_id, + * 再释放槽(顺序不可反,否则 ipc_id 已被清 0)。 */ oldest_idx = -1; oldest = UINT64_MAX; for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) @@ -167,7 +191,13 @@ uint32_t meow_notify_send(const meow_notify *n) oldest = g_slots[i].created_ms; oldest_idx = i; } - if (oldest_idx >= 0) { g_slots[oldest_idx].active = 0; g_slots[oldest_idx].id = 0; } + if (oldest_idx >= 0) { + replaces_id = g_slots[oldest_idx].ipc_id; /* 复用被丢最旧的 launcher nid */ + if (replaces_id == 0) replaces_id = 0; /* 未同步时按新建处理 */ + g_slots[oldest_idx].active = 0; + g_slots[oldest_idx].id = 0; + g_slots[oldest_idx].ipc_id = 0; + } } } if (slot_idx < 0) { @@ -184,26 +214,42 @@ uint32_t meow_notify_send(const meow_notify *n) id = g_next_id++; if (g_next_id == 0) g_next_id = 1; /* 防回绕 */ notify_fill(&g_slots[slot_idx], n, id, now); - notify_deliver(&g_slots[slot_idx], "send"); pthread_mutex_unlock(&g_mu); + + /* 落点(持锁外):IPC 送 launcher 中央存储,回读 nid;无 launcher 则降级(nid=0)。 */ + nid = notify_deliver(n, replaces_id); + + /* 回填 launcher 分配的 nid(槽可能已被并发 cancel/过期清掉——仅在时回填)。 */ + if (nid) { + pthread_mutex_lock(&g_mu); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) + if (g_slots[i].active && g_slots[i].id == id) { g_slots[i].ipc_id = nid; break; } + pthread_mutex_unlock(&g_mu); + } return id; } int meow_notify_cancel(uint32_t id) { int i, rc = 0; + uint32_t ipc_id = 0; if (id == 0) return 0; pthread_mutex_lock(&g_mu); notify_purge_expired(); for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) { if (g_slots[i].active && g_slots[i].id == id) { + ipc_id = g_slots[i].ipc_id; /* launcher 侧 nid;0=未同步/降级 */ g_slots[i].active = 0; g_slots[i].id = 0; + g_slots[i].ipc_id = 0; rc = 1; break; } } pthread_mutex_unlock(&g_mu); + /* 落点:经 IPC MSG_NOTIFY_DISMISS(0x0022) 撤下 launcher 中央存储对应通知 + * (best-effort;无 launcher/未同步则静默降级)。 */ + if (rc && ipc_id) meow_notify_ipc_dismiss(ipc_id); return rc; } diff --git a/meow_ui_sys.c b/meow_ui_sys.c index 07c5ef8..c271f5e 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -14,6 +14,7 @@ */ #include "meow_ui_sys.h" #include "meow_ui_internal.h" +#include "meow_ui_vol.h" #include #include @@ -23,7 +24,6 @@ #include #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" -#define VOL_DEFAULT 60 /* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */ #define BT_REFRESH_MS 30000 @@ -80,22 +80,15 @@ static int hci_powered(void) return up; } -/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 */ +/* 音量:读 settings.conf 的 audio.volume(launcher 同源,watchdog 写同文件)。 + * 与 launcher 口径一致:0-100,且尊重 audio.volume_max 上限 clamp(vol_clamp 同义)。 + * 注意 settings 落盘为 "key = value"(带空格),必须按 '=' 切分解析,不能朴素前缀匹配—— + * 否则永远读不到(回退默认 60,即「状态栏音量数字不对、卡在 60」的根因)。 */ static int settings_vol(void) { - FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); - if (!f) return VOL_DEFAULT; - int vol = VOL_DEFAULT; - char buf[128]; - while (fgets(buf, sizeof buf, f)) { - if (!strncmp(buf, "audio.volume=", 13)) { - int v = atoi(buf + 13); - if (v >= 0 && v <= 100) vol = v; - break; - } - } - fclose(f); - return vol; + int vol = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume", MEOW_UI_VOL_DEFAULT); + int vol_max = meow_ui_vol_read(MEOW_SETTINGS_FILE, "audio.volume_max", MEOW_UI_VOL_MAX_DEF); + return meow_ui_vol_clamp(vol, vol_max); } int meow_ui_sys_init(void) @@ -210,21 +203,25 @@ void meow_ui_sys_statusbar_render(void) /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */ meow_ui_fill_rect(0, 0, w, h, th->bg); + /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */ + int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2; + if (ty < 1) ty = 1; + /* 时钟(左):每秒现取 localtime */ char timebuf[16]; time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - meow_ui_text(8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, timebuf, MUI_SBAR_FONT, th->fg); + meow_ui_text(8, ty, timebuf, MUI_SBAR_FONT, th->fg); /* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */ char right[96]; statusbar_right_str(right, sizeof right); if (right[0]) - meow_ui_text_right(w - 8, (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2, right, MUI_SBAR_FONT, th->fg); + meow_ui_text_right(w - 8, ty, right, MUI_SBAR_FONT, th->fg); - /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=40 从 y=40 起即干净 */ + /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */ meow_ui_fill_rect(0, h - 1, w, 1, th->dim); g_statusbar_force = 0; diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 61561b3..8caeb68 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -10,7 +10,8 @@ * - 电量 /sys/class/power_supply/battery/{capacity,status} * - WiFi /sys/class/net/wlan0/operstate * - 蓝牙 实际 powered(hciconfig -a hci0 的 UP;spawn 贵 → 缓存 30s 刷一次) - * - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件) + * - 音量 /usr/data/meow/settings.conf 的 audio.volume(launcher 同文件; + * 0-100 并 clamp 到 audio.volume_max 上限,与 launcher vol_clamp 口径一致) * - 时钟 localtime_r(每秒在绘制时现取) */ #ifndef MEOW_UI_SYS_H @@ -28,9 +29,10 @@ typedef struct meow_sys_state { int vol; /* 音量 0-100 */ } meow_sys_state; -/* 顶部状态栏几何(与 launcher SB_BAR_H=40 / SB_FONT=28 对齐,视觉统一)。 */ -#define MUI_SBAR_H 40 -#define MUI_SBAR_FONT 28 +/* 顶部状态栏几何(与 launcher SB_BAR_H=28 / SB_FONT=20 对齐,视觉统一)。 + * 高度 40→28(用户定),字号 28→20(28px 栏放不下 28 号;20 号按 (高-行高)/2 垂直居中)。 */ +#define MUI_SBAR_H 28 +#define MUI_SBAR_FONT 20 /* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */ int meow_ui_sys_init(void); @@ -44,7 +46,7 @@ const meow_sys_state *meow_ui_sys_get(void); /* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */ int meow_ui_sys_statusbar_active(void); -/* 状态栏高度(px):active 时 40,否则 0(供 meow_ui_statusbar_height 用)。 */ +/* 状态栏高度(px):active 时 28,否则 0(供 meow_ui_statusbar_height 用)。 */ int meow_ui_sys_statusbar_height(void); /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 diff --git a/meow_ui_vol.c b/meow_ui_vol.c new file mode 100644 index 0000000..b428653 --- /dev/null +++ b/meow_ui_vol.c @@ -0,0 +1,52 @@ +/* + * meow_ui_vol.c — SDK 状态栏音量换算纯函数 + settings.conf 读取(A-0 核心数源)。 + * + * 单独成 TU 的目的: + * 1. 纯函数(clamp / 键读取)不依赖 meow_ui 其它子系统,宿主 gcc 直接可测 + * (test_meow_ui_vol.c),无需链整库/stub。 + * 2. 把「音量从 settings 读数并 clamp 到 volume_max」这一语义钉死在单点, + * 与 launcher 的 vol_clamp(meow_launcherd.c)口径对齐,杜绝两端音量漂移。 + * + * 数据源:/usr/data/meow/settings.conf 扁平 key=value。launcher meow_settings.c + * 落盘为 "key = value"(带空格),本解析兼容无空格/多空格,与 launcher 读循环 + * 及 sdk meow_ui_theme.c 的 get_setting_str 同款解析。 + */ +#include "meow_ui_vol.h" + +#include +#include +#include + +int meow_ui_vol_clamp(int vol, int vol_max) +{ + if (vol < 0) vol = 0; + if (vol > MEOW_UI_VOL_MAX) vol = MEOW_UI_VOL_MAX; + if (vol_max < 0) vol_max = 0; /* 异常上限兜底:不进负值 */ + if (vol > vol_max) vol = vol_max; + return vol; +} + +int meow_ui_vol_read(const char *path, const char *key, int def) +{ + if (!path || !key) return def; + FILE *f = fopen(path, "r"); + if (!f) return def; + int kl = (int)strlen(key); + char line[160]; + char *found = NULL; + while (fgets(line, sizeof line, f)) { + char *eq = strchr(line, '='); + if (!eq) continue; /* 非 key=value 行(注释/空行)跳过 */ + char *k = line; + while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */ + int n = (int)(eq - k); + while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--; /* key 右去空白 */ + if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* key 不精确匹配 */ + found = eq + 1; /* 命中所求键 */ + break; + } + fclose(f); + if (!found) return def; + while (*found == ' ' || *found == '\t') found++; /* value 左去空白;atoi 天然停于非数字 */ + return atoi(found); +} diff --git a/meow_ui_vol.h b/meow_ui_vol.h new file mode 100644 index 0000000..9a94bed --- /dev/null +++ b/meow_ui_vol.h @@ -0,0 +1,32 @@ +/* + * meow_ui_vol.h — SDK 状态栏音量换算/读取(meow_ui_vol.c)。 + * 不随 meow_ui.h 发布给应用;仅供 meow_ui_sys.c 与宿主单测(test_meow_ui_vol.c)使用。 + */ +#ifndef MEOW_UI_VOL_H +#define MEOW_UI_VOL_H + +/* 音量语义常量(与 launcher MEOW_VOL_MAX/DEFAULT、meow_settings 表逐一致): + * audio.volume 默认 60,取值域 0-100(硬上限 MEOW_UI_VOL_MAX); + * audio.volume_max 默认 100(软上限,clamp 上限,用户可调小); + * audio.volume_step 默认 1(跳步;只影响「每次音量键改多少」,与状态栏展示无关—— + * SDK 只读当前值,步进由改动方(launcher)承担)。 */ +#define MEOW_UI_VOL_DEFAULT 60 +#define MEOW_UI_VOL_MAX 100 +#define MEOW_UI_VOL_MAX_DEF 100 + +/* 纯函数:把软件音量夹到 [0, MEOW_UI_VOL_MAX],再夹到 volume_max 软上限。 + * 语义与 launcher 的 vol_clamp(meow_launcherd.c)一致: + * - 硬上限 100(0-100 口径); + * - 尊重 audio.volume_max(用户把上限调小则音量被夹到该值); + * - volume_max 异常(<0)兜底为 0(不进负值)。 + * 无文件/全局依赖,宿主 gcc 可直接测。 */ +int meow_ui_vol_clamp(int vol, int vol_max); + +/* 读 settings.conf 里一个 int 键。扁平 "key=value" 格式(launcher meow_settings.c + * 落盘为 "key = value",带空格;这里兼容无空格/多空格两种写式)。 + * 解析:'=' 切分,key/value 去首尾空白,key 精确匹配(避免 audio.volume 误配 + * audio.volume_max)。未命中/打开失败/键缺失回退 def。 + * path 由调用方传入(设备传导 MEOW_SETTINGS_FILE;宿主单测指向临时文件)。 */ +int meow_ui_vol_read(const char *path, const char *key, int def); + +#endif /* MEOW_UI_VOL_H */ diff --git a/test_meow_ui_vol.c b/test_meow_ui_vol.c new file mode 100644 index 0000000..cf1d7a3 --- /dev/null +++ b/test_meow_ui_vol.c @@ -0,0 +1,145 @@ +/* + * test_meow_ui_vol.c — 宿主单元测试:SDK 状态栏音量换算纯函数 + settings.conf 读取。 + * + * 直接编译 meow_ui_vol.c(纯函数,无 meow_ui 依赖),用临时 settings 文件验证: + * V1 解析:launcher 落盘 "key = value"(带空格)形式能正确读到值(根因回归—— + * 旧代码朴素前缀匹配 "audio.volume=" 永远读不到 → 卡默认 60); + * V2 解析:无空格 "key=value" / 多空格 / 头注释行 / 键缺失回退默认 / 精确匹配 + * (audio.volume 不误配 audio.volume_max); + * V3 clamp:volume_max 上限、硬上限 100、负值兜底 0、上限异常(<0)兜底 0; + * V4 管道(镜像 meow_ui_sys.c settings_vol):读 + clamp 到 volume_max; + * V5 展示串:与 meow_ui_sys.c statusbar_right_str 同款 "VOL %d%%",断言显示值即 + * 当前夹后音量(含 step 语义:a.volume_step 只决定步进,展示原样不/舍入)。 + * + * 编译(宿主 gcc,mipsel 无关): + * gcc -o test_meow_ui_vol test_meow_ui_vol.c meow_ui_vol.c -I. + */ +#include "meow_ui_vol.h" + +#include +#include +#include +#include + +static int failures = 0; +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +/* 写一份临时 settings 文件(lines 为已含 '\n' 的行内容;NULL 结束) */ +static const char *tmp_path; +static void write_settings(const char **lines) +{ + FILE *f = fopen(tmp_path, "w"); + if (!f) { perror("fopen"); exit(2); } + for (int i = 0; lines[i]; i++) fputs(lines[i], f); + fclose(f); +} + +/* 镜像 meow_ui_sys.c 的 settings_vol():读 audio.volume + volume_max,再 clamp。 */ +static int settings_vol_of(const char *path) +{ + int vol = meow_ui_vol_read(path, "audio.volume", MEOW_UI_VOL_DEFAULT); + int vol_max = meow_ui_vol_read(path, "audio.volume_max", MEOW_UI_VOL_MAX_DEF); + return meow_ui_vol_clamp(vol, vol_max); +} + +/* 状态栏右侧 VOL 串组装(与 meow_ui_sys.c statusbar_right_str 同款)。 */ +static void vol_display(int vol, int vol_max, char *out, size_t n) +{ + snprintf(out, n, "VOL %d%%", meow_ui_vol_clamp(vol, vol_max)); +} + +int main(void) +{ + char base[64]; + snprintf(base, sizeof base, "/tmp/meow_vol_%d.conf", (int)getpid()); + tmp_path = base; + + /* ---- V1 解析:launcher 落盘格式 "key = value"(带空格)——根因回归 ---- */ + { + const char *L[] = { "# meow-settings 1\n", "audio.volume = 82\n", NULL }; + write_settings(L); + check("V1 launcher 空格格式 audio.volume=82 -> 82", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 82); + } + + /* ---- V2 解析:无空格 / 多空格 / 注释 / 缺键 / 精确匹配 ---- */ + { + const char *L[] = { "audio.volume=40\n", NULL }; + write_settings(L); + check("V2 无空格 audio.volume=40 -> 40", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 40); + + const char *L2[] = { " audio.volume = 7 \n", NULL }; + write_settings(L2); + check("V2 多空格 audio.volume = 7 -> 7", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == 7); + + const char *L3[] = { "display.brightness = 100\n", NULL }; + write_settings(L3); + check("V2 键缺失 -> 默认 -1", meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1); + + const char *L4[] = { "audio.volume_max = 90\n", NULL }; + write_settings(L4); + check("V2 audio.volume 不误配 audio.volume_max -> 默认", + meow_ui_vol_read(tmp_path, "audio.volume", -1) == -1); + check("V2 单独读 audio.volume_max=90 -> 90", + meow_ui_vol_read(tmp_path, "audio.volume_max", -1) == 90); + } + + /* ---- V3 clamp 纯函数 ---- */ + check("V3 clamp(60,100)=60", meow_ui_vol_clamp(60, 100) == 60); + check("V3 clamp(80,60)=60 (尊重 volume_max)", meow_ui_vol_clamp(80, 60) == 60); + check("V3 clamp(80,100)=80", meow_ui_vol_clamp(80, 100) == 80); + check("V3 clamp(150,100)=100 (硬上限)", meow_ui_vol_clamp(150, 100) == 100); + check("V3 clamp(150,80)=80", meow_ui_vol_clamp(150, 80) == 80); + check("V3 clamp(-5,100)=0 (负值兜底)", meow_ui_vol_clamp(-5, 100) == 0); + check("V3 clamp(60,0)=0 (上限 0)", meow_ui_vol_clamp(60, 0) == 0); + check("V3 clamp(60,-10)=0 (上限异常兜底)", meow_ui_vol_clamp(60, -10) == 0); + check("V3 clamp(120,255)=100 (硬上限优先于大上限)", meow_ui_vol_clamp(120, 255) == 100); + + /* ---- V4 管道:读 + clamp 到 volume_max(镜像 settings_vol)---- */ + { + const char *L[] = { "audio.volume = 82\n", "audio.volume_max = 100\n", NULL }; + write_settings(L); + check("V4 vol=82 max=100 -> 82", settings_vol_of(tmp_path) == 82); + + const char *L2[] = { "audio.volume = 82\n", "audio.volume_max = 60\n", NULL }; + write_settings(L2); + check("V4 vol=82 max=60 -> 60 (clamp)", settings_vol_of(tmp_path) == 60); + + const char *L3[] = { "audio.volume = 150\n", "audio.volume_max = 100\n", NULL }; + write_settings(L3); + check("V4 vol=150 max=100 -> 100 (硬上限)", settings_vol_of(tmp_path) == 100); + + const char *L4[] = { "audio.volume = -3\n", "audio.volume_max = 100\n", NULL }; + write_settings(L4); + check("V4 vol=-3 -> 0", settings_vol_of(tmp_path) == 0); + + const char *L5[] = { "display.brightness = 100\n", NULL }; /* 无音量键 */ + write_settings(L5); + check("V4 无 audio.volume -> 默认 60", settings_vol_of(tmp_path) == MEOW_UI_VOL_DEFAULT); + } + + /* ---- V5 展示串:VOL %d%(step 只决定步进,展示原样不/舍入)---- */ + { + char s[16]; + vol_display(60, 100, s, sizeof s); + check("V5 vol=60 -> \"VOL 60%\"", !strcmp(s, "VOL 60%")); + vol_display(80, 60, s, sizeof s); + check("V5 vol=80 max=60 -> \"VOL 60%\" (显示夹后值)", !strcmp(s, "VOL 60%")); + vol_display(-5, 100, s, sizeof s); + check("V5 vol=-5 -> \"VOL 0%\"", !strcmp(s, "VOL 0%")); + vol_display(37, 100, s, sizeof s); + check("V5 vol=37 -> \"VOL 37%\" (step 值原样展示)", !strcmp(s, "VOL 37%")); + } + + unlink(tmp_path); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/test_notify_integration.c b/test_notify_integration.c new file mode 100644 index 0000000..6ea53d9 --- /dev/null +++ b/test_notify_integration.c @@ -0,0 +1,202 @@ +/* + * test_notify_integration.c — 宿主集成测试:meow_notify_send/cancel 全链路(任务 #5)。 + * + * 直接把 meow_ui_notify.c + meow_notify_ipc.c 编进来,仅 stub 两个 SDK 能力 + * (meow_log 空实现 / meow_ui_time_ms 单调毫秒),用本地 AF_UNIX stub socket 回环: + * + * S1 send(general,timeout=0) → 返回本地 id(1),stub 回 nid=101,active_count=1; + * S2 send(media) → media=1、category=media(经 stub 捕获帧校验); + * S3 同类型再发(未满配额)→ 返回本地 id(3); + * S4 cancel(本地id=1) → 返回 1,stub 收到 MSG_NOTIFY_DISMISS(负载 nid=101); + * S5 配额满(每类型4)后发第 5 条 → 丢最旧:该 5 条 send 的 replaces_id == 被丢最旧的 nid; + * S6 无 launcher(connect 失败)→ send 仍返回本地 id(>0)、不崩;cancel 也静默不崩。 + * + * 编译(宿主 gcc): + * gcc -o test_notify_integration test_notify_integration.c \ + * meow_ui_notify.c meow_notify_ipc.c -I. -I../meow_launcherd -lpthread + */ +#include "meow_ui.h" +#include "meow_notify_ipc.h" +#include "meow_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---- stub SDK 依赖(避免链整库) ---- */ +void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } +uint64_t meow_ui_time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; +} + +/* ---- stub server 捕获(每次连接一帧) ---- */ +#define MAXCAP 16 +typedef struct { int type; m_notify_t n; uint32_t dismiss_id; uint32_t nid; } srec_t; +static srec_t g_rec[MAXCAP]; +static int g_nrec = 0; +static int g_stop = 0; +static int g_listen_fd = -1; +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; + +static void *server(void *arg) +{ + (void)arg; + while (!g_stop) { + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) { + if (errno == EAGAIN && g_stop) break; /* 非阻塞:listener 被关闭 */ + if (errno == EBADF) break; + usleep(1000); + continue; + } + uint8_t hdr[MEOW_IPC_HDR_LEN]; + size_t off = 0; + while (off < sizeof hdr) { ssize_t r = read(c, hdr + off, sizeof hdr - off); if (r <= 0) break; off += (size_t)r; } + if (off < sizeof hdr) { close(c); continue; } + uint32_t type, len, req_id; + memcpy(&req_id, hdr + 8, 4); + memcpy(&len, hdr + 12, 4); + memcpy(&type, hdr + 6, 2); + if (len > MEOW_IPC_MAX) { close(c); continue; } + uint8_t pay[MEOW_IPC_MAX]; + off = 0; + while (off < len) { ssize_t r = read(c, pay + off, len - off); if (r <= 0) break; off += (size_t)r; } + + pthread_mutex_lock(&g_mu); + int idx = g_nrec < MAXCAP ? g_nrec++ : (MAXCAP - 1); + memset(&g_rec[idx], 0, sizeof g_rec[idx]); + g_rec[idx].type = (int)type; + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_rec[idx].n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_rec[idx].dismiss_id, pay, 4); + /* 分配 nid = 100 + NOTIFY 序号 */ + uint32_t seq = 0; + for (int i = 0; i <= idx; i++) if (g_rec[i].type == MSG_NOTIFY) seq++; + g_rec[idx].nid = (type == MSG_NOTIFY) ? (100 + seq) : 0; + uint32_t ack_nid = g_rec[idx].nid; + pthread_mutex_unlock(&g_mu); + + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = ack_nid; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = (uint16_t) + (type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); uint32_t rl = sizeof a; + memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4); + size_t w0 = 0; while (w0 < sizeof rhdr) { ssize_t w = write(c, rhdr+w0, sizeof rhdr-w0); if (w<=0) break; w0 += (size_t)w; } + size_t w1 = 0; while (w1 < rl) { ssize_t w = write(c, (uint8_t*)&a+w1, rl-w1); if (w<=0) break; w1 += (size_t)w; } + } + close(c); + } + return NULL; +} + +static int g_listen_made = 0; +static int make_listen(char *path, size_t n) +{ + snprintf(path, n, "/tmp/meow_notify_intg_%d.sock", (int)getpid()); + unlink(path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un a; memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; } + fcntl(fd, F_SETFL, O_NONBLOCK); /* 非阻塞 listener:accept() 可被 g_stop 截断 */ + g_listen_fd = fd; g_listen_made = 1; + return 0; +} +static void stop_server(void) +{ + if (g_listen_made) { g_stop = 1; close(g_listen_fd); g_listen_made = 0; } +} + +static srec_t rec_at(int i) +{ + srec_t r; pthread_mutex_lock(&g_mu); r = g_rec[i]; pthread_mutex_unlock(&g_mu); return r; +} + +static int failures = 0; +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +int main(void) +{ + setenv("MEOW_LOG_APP", "intgapp", 1); + char sock[108]; + if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; pthread_create(&th, NULL, server, NULL); + + /* S1 通用通知 */ + meow_notify nt; memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "t1"; nt.timeout_ms = 0; + uint32_t id1 = meow_notify_send(&nt); + check("S1 send 返回本地 id=1", id1 == 1); + check("S1 active_count=1", meow_notify_active_count() == 1); + check("S1 stub 回 nid=101", rec_at(0).nid == 101); + + /* S2 媒体通知:单例 + media=1 */ + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.title = "music"; nt.timeout_ms = -1; + uint32_t id2 = meow_notify_send(&nt); + check("S2 媒体返本地 id=2", id2 == 2); + check("S2 category=media", !strcmp(rec_at(1).n.category, "media")); + check("S2 media=1", rec_at(1).n.media == 1); + check("S2 expire=0xFFFFFFFF", rec_at(1).n.expire_timeout == 0xFFFFFFFFu); + + /* S3 同类型再发(general 未满 4)→ 新本地 id */ + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "t3"; nt.timeout_ms = 0; + uint32_t id3 = meow_notify_send(&nt); + check("S3 再发返本地 id=3", id3 == 3); + check("S3 第2条general nid=103", rec_at(2).nid == 103); + + /* S4 cancel(id1) → DISMISS nid=101 */ + int rc = meow_notify_cancel(id1); + check("S4 cancel 返回 1", rc == 1); + check("S4 stub 收到 MSG_NOTIFY_DISMISS", rec_at(3).type == MSG_NOTIFY_DISMISS); + check("S4 dismiss 负载 nid=101", rec_at(3).dismiss_id == 101); + check("S4 本地表已撤(count=2)", meow_notify_active_count() == 2); + + /* S5 配额满 → 丢最旧 + replaces_id 复用被丢 nid。 + 当前 general 有 id3(1 条);再发 4 条到 4 条(第 4 条触顶后丢最旧)。 */ + for (int i = 0; i < 4; i++) { + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "quota"; nt.timeout_ms = 0; + (void)meow_notify_send(&nt); + } + /* 现在 general 应仍有 4 条(第5条丢最旧 id3=本地,即被降级替换) */ + check("S5 配额满后每类型仍=4", meow_notify_count_in_type(MEOW_NOTIFY_CAT_GENERAL) == 4); + /* 被丢最旧 = id3(第2条 general,本地 id=3),其 launcher nid=103(rec[2])。 + 触发丢最旧的是 S5 循环第 4 次 send = 第 7 条 NOTIFY(rec[7]),其 replaces_id==103。 */ + check("S5 丢最旧后 send replaces_id=被丢nid(103)", rec_at(7).n.replaces_id == 103); + + stop_server(); + pthread_join(th, NULL); + + /* S6 无 launcher 降级 */ + char dead[108]; snprintf(dead, sizeof dead, "/tmp/meow_notify_intg_dead_%d.sock", (int)getpid()); + unlink(dead); + setenv("MEOW_IPC_SOCK", dead, 1); + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_EMAIL; nt.title = "no-launcher"; nt.timeout_ms = 0; + uint32_t id6 = meow_notify_send(&nt); + check("S6 无 launcher 仍返回本地 id(>0)", id6 > 0); + check("S6 无 launcher active_count 正常", meow_notify_active_count() >= 1); + check("S6 cancel 无 launcher 静默不崩", meow_notify_cancel(id6) == 1); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/test_notify_ipc.c b/test_notify_ipc.c new file mode 100644 index 0000000..f1ba227 --- /dev/null +++ b/test_notify_ipc.c @@ -0,0 +1,281 @@ +/* + * test_notify_ipc.c — 宿主测试:任务 #5 SDK↔IPC 桥(meow_notify_ipc.{h,c})。 + * + * 用本地 AF_UNIX stub socket(回环)验证: + * T1 枚举↔字符串映射:CAT_PROGRESS→"progress"、CAT_MEDIA→"media" + media=1; + * T2 timeout 映射:-1(ONGOING)→expire=0xFFFFFFFF + ongoing=1;0(NEVER)→expire=0; + * >0→该毫秒 + ongoing=0; + * T3 回读 MSG_NOTIFY_ACK 拿 launcher 分配的 nid;replaces_id 原样透传;progress 钳位; + * T4 无 socket 降级:connect 失败 → meow_notify_ipc_send 返回 0,不崩; + * T5 dismiss:meow_notify_ipc_dismiss(nid) 发出 MSG_NOTIFY_DISMISS 帧(负载=nid)。 + * + * 编译(宿主 gcc,不依赖 meow_ui.a 其余 TU): + * gcc -o test_notify_ipc test_notify_ipc.c meow_notify_ipc.c \ + * -I. -I../meow_launcherd -lpthread + * 运行:./test_notify_ipc (全部 PASS 退出码 0,任一 FAIL 退出码 1) + */ +#include "meow_notify_ipc.h" +#include "meow_ui.h" +#include "meow_ipc.h" /* 帧头 / m_notify_t / m_ack_t / MSG_*(同源校验) */ + +#include +#include +#include +#include +#include +#include +#include + +/* ---- 捕获:stub 服务端解析到的帧内容(mutex 保护;客户端返回后读取) ---- */ +typedef struct { + uint32_t type; /* 收到的帧类型 */ + m_notify_t n; /* MSG_NOTIFY 负载 */ + uint32_t dismiss_id; /* MSG_NOTIFY_DISMISS 负载 */ + uint32_t req_id; + uint32_t nid_to_return; /* ACK 中回填的 status */ + int got; /* 已收到一帧 */ +} cap_t; +static cap_t g_cap; +static pthread_mutex_t g_cap_mu = PTHREAD_MUTEX_INITIALIZER; + +static void cap_store(uint32_t type, uint32_t req_id, const uint8_t *pay, uint32_t len, + uint32_t nid_to_return) +{ + pthread_mutex_lock(&g_cap_mu); + memset(&g_cap, 0, sizeof g_cap); + g_cap.type = type; + g_cap.req_id = req_id; + g_cap.nid_to_return = nid_to_return; + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_cap.n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_cap.dismiss_id, pay, 4); + g_cap.got = 1; + pthread_mutex_unlock(&g_cap_mu); +} + +/* ---- stub 服务端线程:accept 一个连接 → 读一帧 → 校验由主线程做,这里只收 + 回 ACK ---- */ +static int g_listen_fd = -1; +static void *server_thread(void *arg) +{ + (void)arg; + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) return NULL; + /* 读 16B 头 */ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t off = 0; + while (off < sizeof hdr) { + ssize_t r = read(c, hdr + off, sizeof hdr - off); + if (r <= 0) { close(c); return NULL; } + off += (size_t)r; + } + uint32_t magic, len; + uint16_t ver, type; + memcpy(&magic, hdr, 4); + memcpy(&ver, hdr + 4, 2); + memcpy(&type, hdr + 6, 2); + memcpy(&len, hdr + 12, 4); + uint32_t req_id; memcpy(&req_id, hdr + 8, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { close(c); return NULL; } + if (len > MEOW_IPC_MAX) { close(c); return NULL; } + uint8_t pay[MEOW_IPC_MAX]; + off = 0; + while (off < len) { + ssize_t r = read(c, pay + off, len - off); + if (r <= 0) { close(c); return NULL; } + off += (size_t)r; + } + cap_store(type, req_id, pay, len, (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0)); + + /* MSG_NOTIFY → 回 m_ack_t(code=OK, status=nid_to_return); + * MSG_NOTIFY_DISMISS → 回 m_ack_t(code=OK, status=0,同 launcher 行为)。 */ + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + m_ack_t a; + memset(&a, 0, sizeof a); + a.code = MEOW_OK; + a.status = (type == MSG_NOTIFY) ? (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0) : 0; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t rmagic = MEOW_IPC_MAGIC; + uint16_t rver = MEOW_IPC_VERSION, rtype = (uint16_t)(type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); + uint32_t rlen = sizeof a; + memcpy(rhdr + 0, &rmagic, 4); + memcpy(rhdr + 4, &rver, 2); + memcpy(rhdr + 6, &rtype, 2); + memcpy(rhdr + 8, &req_id, 4); + memcpy(rhdr + 12, &rlen, 4); + off = 0; + while (off < sizeof rhdr) { ssize_t w = write(c, rhdr + off, sizeof rhdr - off); if (w <= 0) break; off += (size_t)w; } + off = 0; + while (off < rlen) { ssize_t w = write(c, (uint8_t*)&a + off, rlen - off); if (w <= 0) break; off += (size_t)w; } + } + close(c); + return NULL; +} + +static int failures = 0; + +static void check(const char *name, int cond) +{ + printf("%s %s\n", cond ? "PASS" : "FAIL", name); + if (!cond) failures++; +} + +/* 建 listening socket,返回 fd;路径写入 sock_path。 */ +static int arb_listen(char *sock_path, size_t slen) +{ + static int count = 0; + snprintf(sock_path, slen, "/tmp/meow_notify_ipc_%d_%d.sock", (int)getpid(), count++); + unlink(sock_path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { perror("socket"); return -1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, sock_path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr *)&a, sizeof a) < 0) { perror("bind"); close(fd); return -1; } + if (listen(fd, 1) < 0) { perror("listen"); close(fd); return -1; } + return fd; +} + +int main(void) +{ + setenv("MEOW_LOG_APP", "testapp", 1); /* 固定 app_id,供 stub 校验 */ + + /* ---------- T1/T2/T3:映射 + ACK 回 nid ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + if (g_listen_fd < 0) { fprintf(stderr, "cannot create listener\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x1234", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_PROGRESS; + nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "下载中"; + nt.body = "70%"; + nt.timeout_ms = 5000; /* >0 → expire=5000, ongoing=0 */ + nt.progress = 150; /* 钳位 → 100 */ + uint32_t nid = meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + + check("T3 send 返回 launcher nid(0x1234)", nid == 0x1234); + pthread_mutex_lock(&g_cap_mu); + int got = g_cap.got; + const m_notify_t *cn = &g_cap.n; + pthread_mutex_unlock(&g_cap_mu); + check("T3 stub 收到 MSG_NOTIFY 帧", got && g_cap.type == MSG_NOTIFY); + check("T1 CAT_PROGRESS→category=progress", !strcmp(cn->category, "progress")); + check("T2 timeout>0→expire_timeout=5000", cn->expire_timeout == 5000); + check("T2 timeout>0→ongoing=0", cn->ongoing == 0); + check("T1 media=0(非媒体)", cn->media == 0); + check("T1 progress 钳位 150→100", cn->progress == 100); + check("T2 replaces_id 透传=0", cn->replaces_id == 0); + check("T1 app_id=testapp", !strcmp(cn->app_id, "testapp")); + check("T1 summary=下载中", !strcmp(cn->summary, "下载中")); + } + + /* ---------- T1/T2:媒体类型 + ONGOING(-1) ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x9999", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; + nt.urgency = MEOW_NOTIFY_URG_CRITICAL; + nt.title = "Now Playing"; + nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; /* -1 */ + uint32_t nid = meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + + check("T3 send(media/ongoing) 返回 nid(0x9999)", nid == 0x9999); + check("T1 CAT_MEDIA→category=media", !strcmp(g_cap.n.category, "media")); + check("T1 CAT_MEDIA→media=1", g_cap.n.media == 1); + check("T2 ONGOING(-1)→expire=0xFFFFFFFF", g_cap.n.expire_timeout == 0xFFFFFFFFu); + check("T2 ONGOING(-1)→ongoing=1", g_cap.n.ongoing == 1); + } + + /* ---------- T2:NEVER(0) 超时 ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0x1", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_GENERAL; + nt.title = "x"; + nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER; /* 0 */ + (void)meow_notify_ipc_send(&nt, 0); + pthread_join(th, NULL); + close(g_listen_fd); + check("T2 NEVER(0)→expire=0 & ongoing=0", + g_cap.n.expire_timeout == 0 && g_cap.n.ongoing == 0); + check("T1 CAT_GENERAL→category=general", !strcmp(g_cap.n.category, "general")); + } + + /* ---------- T3:replaces_id 透传 ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + setenv("TEST_NID", "0xBEEF", 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MESSAGE; + nt.title = "repl"; + nt.timeout_ms = 0; + (void)meow_notify_ipc_send(&nt, 0x42u); + pthread_join(th, NULL); + close(g_listen_fd); + check("T3 replaces_id 透传=0x42", g_cap.n.replaces_id == 0x42u); + } + + /* ---------- T4:无 socket 降级不崩 ---------- */ + { + /* 指向不存在路径 */ + char dead[108]; + snprintf(dead, sizeof dead, "/tmp/meow_notify_ipc_dead_%d.sock", (int)getpid()); + unlink(dead); + setenv("MEOW_IPC_SOCK", dead, 1); + meow_notify nt; + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_EMAIL; + nt.title = "no-socket"; + nt.timeout_ms = 0; + uint32_t nid = meow_notify_ipc_send(&nt, 0); + check("T4 无 socket→返回 0(降级不崩)", nid == 0); + meow_notify_ipc_dismiss(0x1234); /* 无 socket 静默,不崩 */ + check("T4 dismiss 无 socket 静默不崩", 1); + } + + /* ---------- T5:dismiss 发 MSG_NOTIFY_DISMISS ---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify_ipc_dismiss(0xABCDu); + pthread_join(th, NULL); + close(g_listen_fd); + check("T5 stub 收到 MSG_NOTIFY_DISMISS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_DISMISS); + check("T5 dismiss 负载=nid=0xABCD", g_cap.dismiss_id == 0xABCDu); + } + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", + failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} diff --git a/test_notify_post.c b/test_notify_post.c new file mode 100644 index 0000000..beac070 --- /dev/null +++ b/test_notify_post.c @@ -0,0 +1,150 @@ +/* + * test_notify_post.c — 宿主交叉验证:SDK send 的 IPC 帧 → launcher 真·中央存储。 + * + * 把 SDK 的 meow_notify_ipc.c(映射+传输)与 launcher 的 meow_notif.c(真·中央存储, + * meow_notif_post/dismiss)接到一起,stub 掉 meow_settings 后端;服务端复刻 + * meow_launcherd.c:6427-6440 的 m_notify_t→meow_notif_t 映射 + MSG_NOTIFY_ACK 应答。 + * + * 验证:SDK meow_notify_ipc_send() 产出的帧被 meow_notif_post() 真实落库并回分配 nid>0; + * 随后 meow_notify_ipc_dismiss() 经 meow_notif_dismiss() 撤下(count 归 0)。 + * + * 编译(宿主 gcc): + * gcc -o test_notify_post test_notify_post.c meow_notify_ipc.c \ + * ../meow_launcherd/meow_notif.c -I. -I../meow_launcherd -lpthread + */ +#include "meow_notify_ipc.h" +#include "meow_ui.h" +#include "meow_ipc.h" +#include "meow_notif.h" /* launcher 真·中央存储 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---- stub:settings 后端(键缺省 → meow_notif.c 用默认配额) ---- */ +int meow_settings_get_int(const char *key, int *out) { (void)key; (void)out; return MEOW_ERR_NOENT; } + +/* ---- 复刻 launcher 的 m_notify_t → meow_notif_t 映射(meow_launcherd.c:6427-6440) ---- + * 注:与 launcher 同款 snprintf(dest 较 src 小会有 format-truncation 提示),launcher 自身 + * 以其编译 flags 构建无警告;此处仅为宿主测试,用 pragma 压掉以免 -Werror 干扰。 */ +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" +#endif +static void launcher_map(const m_notify_t *mn, meow_notif_t *n) +{ + memset(n, 0, sizeof *n); + n->id = mn->replaces_id; /* 0=>新建;>0=>替换(不存在则新建) */ + snprintf(n->app_id, sizeof n->app_id, "%s", mn->app_id); + snprintf(n->category, sizeof n->category, "%s", mn->category); + snprintf(n->summary, sizeof n->summary, "%s", mn->summary); + snprintf(n->body, sizeof n->body, "%s", mn->body); + snprintf(n->app_icon, sizeof n->app_icon, "%s", mn->app_icon); + snprintf(n->target_activity, sizeof n->target_activity, "%s", mn->target_activity); + n->expire_timeout = mn->expire_timeout; + n->progress = mn->progress; + n->ongoing = mn->ongoing; + n->media = mn->media; +} +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +static int g_listen_fd = -1; +static int g_stop = 0; +static uint32_t g_posted_nid = 0; + +static void reply_ack(int c, uint16_t type, uint32_t req_id, uint32_t status) +{ + m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = status; + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = type; uint32_t rl = sizeof a; + memcpy(rhdr+0,&mag,4); memcpy(rhdr+4,&ver,2); memcpy(rhdr+6,&t,2); memcpy(rhdr+8,&req_id,4); memcpy(rhdr+12,&rl,4); + size_t o = 0; while (o < sizeof rhdr) { ssize_t w = write(c, rhdr+o, sizeof rhdr-o); if (w<=0) break; o += (size_t)w; } + o = 0; while (o < rl) { ssize_t w = write(c, (uint8_t*)&a+o, rl-o); if (w<=0) break; o += (size_t)w; } +} + +static void *server(void *arg) +{ + (void)arg; + meow_notif_init(); + while (!g_stop) { + int c = accept(g_listen_fd, NULL, NULL); + if (c < 0) { if (errno == EAGAIN && g_stop) break; if (errno == EBADF) break; usleep(1000); continue; } + uint8_t hdr[MEOW_IPC_HDR_LEN]; size_t o = 0; + while (o < sizeof hdr) { ssize_t r = read(c, hdr+o, sizeof hdr-o); if (r<=0) break; o += (size_t)r; } + if (o < sizeof hdr) { close(c); continue; } + uint32_t type, len, req_id; memcpy(&type, hdr+6, 2); memcpy(&req_id, hdr+8, 4); memcpy(&len, hdr+12, 4); + if (len > MEOW_IPC_MAX) { close(c); continue; } + uint8_t pay[MEOW_IPC_MAX]; o = 0; + while (o < len) { ssize_t r = read(c, pay+o, len-o); if (r<=0) break; o += (size_t)r; } + + if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) { + m_notify_t mn; memcpy(&mn, pay, sizeof mn); + meow_notif_t n; launcher_map(&mn, &n); + uint32_t nid = 0; + int rc = meow_notif_post(&n, &nid); + if (rc == NOTIF_OK) g_posted_nid = nid; + reply_ack(c, MSG_NOTIFY_ACK, req_id, (rc == NOTIF_OK) ? nid : (uint32_t)rc); + fprintf(stderr, "[post-srv] post rc=%d nid=%u cat=%s\n", rc, nid, mn.category); + } else if (type == MSG_NOTIFY_DISMISS && len >= 4) { + uint32_t id; memcpy(&id, pay, 4); + int rc = meow_notif_dismiss(id); + reply_ack(c, MSG_ACK, req_id, 0); + fprintf(stderr, "[post-srv] dismiss id=%u rc=%d\n", id, rc); + } + close(c); + } + return NULL; +} + +static int make_listen(char *path, size_t n) +{ + snprintf(path, n, "/tmp/meow_notify_post_%d.sock", (int)getpid()); + unlink(path); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (bind(fd, (struct sockaddr*)&a, sizeof a) < 0 || listen(fd, 4) < 0) { close(fd); return -1; } + fcntl(fd, F_SETFL, O_NONBLOCK); + g_listen_fd = fd; + return 0; +} + +static int failures = 0; +static void check(const char *name, int cond) { printf("%s %s\n", cond ? "PASS" : "FAIL", name); if (!cond) failures++; } + +int main(void) +{ + setenv("MEOW_LOG_APP", "intgapp", 1); + char sock[108]; + if (make_listen(sock, sizeof sock) != 0) { fprintf(stderr, "cannot listen\n"); return 2; } + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; pthread_create(&th, NULL, server, NULL); + + /* SDK send → launcher 真存储。media + ONGOING 走全部映射分支。 */ + meow_notify nt; memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.title = "正在播放"; nt.timeout_ms = -1; nt.progress = 40; + uint32_t nid = meow_notify_ipc_send(&nt, 0); + check("send 经 launcher 真存储回 nid>0", nid != 0); + check("send 拿到 launcher 分配的 nid(与 post 一致)", g_posted_nid == nid); + check("launcher 存储 count(media app)=1", meow_notif_count("intgapp") == 1); + + /* SDK dismiss → launcher 撤下 */ + meow_notify_ipc_dismiss(nid); + check("dismiss 后 launcher 存储 count=0", meow_notif_count("intgapp") == 0); + + g_stop = 1; close(g_listen_fd); + pthread_join(th, NULL); + meow_notif_shutdown(); + + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s"); + return failures ? 1 : 0; +} From 20c7c8dbbd7b9198aeaa288920e4dfed2993de1f Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 11:28:11 +0800 Subject: [PATCH 03/49] =?UTF-8?q?=E5=9F=BA=E5=BA=A7+=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3(=E4=B8=89=E7=89=B9=E6=80=A7):=20meow=5Fui.h?= =?UTF-8?q?=20=E5=AD=97=E4=BD=93=E6=A1=A3/=E7=8A=B6=E6=80=81=E6=A0=8F?= =?UTF-8?q?=E6=94=B6=E5=90=88API=20+=20launcher=20fonts/fullscreen?= =?UTF-8?q?=E8=A7=A3=E6=9E=90+env=E6=B3=A8=E5=85=A5+=E9=80=9A=E7=9F=A5dump?= =?UTF-8?q?=E9=92=A9=E5=AD=90=20+=20meow=5Fui=5Fgfx.c=20=E6=94=B6=E5=90=88?= =?UTF-8?q?=E6=B8=B2=E6=9F=93+=E5=AD=97=E4=BD=93=E6=A1=A3;=20spec-ref/SYST?= =?UTF-8?q?EM=5FSETTINGS/APPS/NOTIFICATION=20=E4=B8=89=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E8=90=BD=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui.h | 39 +++++++++++++++ meow_ui_gfx.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 5 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index bca5b25..bc17e86 100644 --- a/meow_ui.h +++ b/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/meow_ui_gfx.c b/meow_ui_gfx.c index 83a6abe..c429599 100644 --- a/meow_ui_gfx.c +++ b/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; } From 663b0aa11da94511b138ded5e4070d58cce9d177 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 11:46:29 +0800 Subject: [PATCH 04/49] =?UTF-8?q?=E4=B8=89=E7=89=B9=E6=80=A7:=20=E5=AD=97?= =?UTF-8?q?=E4=BD=93=E5=88=86=E5=B1=82(gen=5Ffont.py=20--charset=20+=20meo?= =?UTF-8?q?w=5Ffont=5Freload=5Ftier=20+=20font.load=5Ffull=20+=20=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E6=A0=87=E8=AE=B0)=20+=20=E5=85=A8=E5=B1=8F=E6=9C=BA?= =?UTF-8?q?=E5=88=B6/=E7=8A=B6=E6=80=81=E6=A0=8F=E6=94=B6=E5=90=88(meow=5F?= =?UTF-8?q?ui=5Fsys/input)=20+=20=E9=80=9A=E7=9F=A5#6=20TF=E8=BD=AC?= =?UTF-8?q?=E5=82=A8(CSV+10MB+=E5=BC=80=E6=9C=BA=E6=B8=85=E6=9D=BF?= =?UTF-8?q?=E8=BD=BD+=E5=86=85=E5=AD=98=E8=A1=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui.h | 14 ++++++++------ meow_ui_gfx.c | 34 +++++++++++++++++++++++----------- meow_ui_input.c | 28 +++++++++++++++++++++++++++- meow_ui_sys.c | 19 ++++++++++++++++--- meow_ui_sys.h | 20 ++++++++++++++++++-- 5 files changed, 92 insertions(+), 23 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index bc17e86..298e379 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -328,22 +328,24 @@ const char *meow_notify_urgency_str(meow_notify_urgency u); const char *meow_notify_category_str(meow_notify_category cat); /* ============================================================ */ -/* §10 字体分层 / 大字库(草案/待定案 —— 本批仅加声明/枚举/宏,不实现) */ +/* §10 字体分层 / 大字库(M2.5x:基座声明 + font 域已实现按档装载) */ /* ============================================================ */ /* 字体「档位」概念:launcher 按 app.json 声明的字库档("fonts":<档>)向 app 注入 - * MEOW_FONT_TIER=<0|1|2>;SDK(meow_ui_gfx / 后续 meow_font 大字库加载)据档选库: + * 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 扩展。 */ + * 大字库生成由 font/gen/gen_font.py --charset 按档产出;按档装载由 meow_font_reload_tier() + * 实现(先 unload 旧再 load 新,释放旧 atlas 防大字库双驻留)。launcher 经 + * meow_font_effective_paths_tiered() 按档解析大字库绝对路径并注入 MEOW_FONT_PNG/IDX。 + * 既有 meow_font_* 符号(class_of/has_glyph/load_slot/config_multifont…)保留不动。 + * 判缺字:meow_font_has_glyph(cp) 预判当前字库是否含该码位。 */ #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 代理实现。 */ + * meow_ui_gfx.c 定义并触发 meow_font_reload_tier() 真重载(大字库切换释放旧 atlas)。 */ int meow_font_set_tier(int tier); /* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */ int meow_font_tier(void); diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index c429599..d6a9798 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -77,8 +77,8 @@ static unsigned long long g_sbar_collapse_start_ms = 0; /* 本次过渡起始 /* ---- 字体档位(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 挂钩实现——此处仅记录档位不换库。 */ + * meow_font_set_tier/tier 暴露。按档装载大字库(GBK/Full)由 meow_font_reload_tier() + * 在 meow_font.c 实现;meow_font_set_tier() 在档位变化时经 env/settings 路由到该钩子真重载。 */ static int g_font_tier = MEOW_FONT_TIER_COMMON; /* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)---- @@ -665,20 +665,32 @@ 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 的钩子处唤起(见下)。 */ +/* ---- 字体档位(公共 API,基座定义 + font 域真重载钩子)---- + * meow_font_tier()/meow_font_set_tier() 在 meow_ui_gfx.c 定义(符号唯一),font 代理勿在 + * meow_font.c 重复定义;大字库按档装载由 meow_font.c meow_font_reload_tier() 实现,并在 + * meow_font_set_tier() 处唤起(见下)。 */ int meow_font_tier(void) { return g_font_tier; } int meow_font_set_tier(int tier) { + int old = g_font_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)。 */ + /* 真重载(font 域钩子,M2.5x):按档装载大字库。launcher 已按档解析并注入 + * MEOW_FONT_PNG/IDX(tier>0 → GBK/Full 大字库绝对路径);重读 env 重载。 + * 无 env/失败 → 回退卡默认 MeowOS/fonts/default → 固件。 + * 大字库 atlas 大(GBK≈5-15MB+),meow_font_reload_tier 先 unload 旧再 load 新,防双驻留。 */ + const char *ep = getenv("MEOW_FONT_PNG"); + const char *ei = getenv("MEOW_FONT_IDX"); + if (ep && ep[0] && ei && ei[0] && meow_font_reload_tier(tier, ep, ei) == 0) + return g_font_tier; + if (meow_font_reload_tier(tier, MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) + return g_font_tier; + if (meow_font_reload_tier(tier, MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) + return g_font_tier; + g_font_tier = old; /* 全部失败 → 回退旧档(字库可能已不可用,5x7 点阵兜底,系统不死) */ return g_font_tier; } @@ -692,9 +704,9 @@ int meow_font_set_tier(int tier) 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)字库,档位仅记录。 */ + * 0=默认4K常用 1=大字库GBK 2=Full。launcher 已按档解析大字库路径并经 MEOW_FONT_PNG/IDX + * 注入(见 meow_font_effective_paths_tiered),故下方 meow_font_load(ep,ei) 即装载 + * 对应档位字库(tier>0 → GBK/Full 大字库);此处 read env 存档对齐公共 get/set。 */ const char *etr = getenv("MEOW_FONT_TIER"); if (etr && etr[0]) { g_font_tier = atoi(etr); diff --git a/meow_ui_input.c b/meow_ui_input.c index fed0fc6..7a155d2 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -38,6 +38,20 @@ #define QUEUE_N 128 #define MAX_DEVS 8 +/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)── + * 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理 + * (路由 meow_ui_sys_notif_pull),不下发给 APP 的 on_swipe(顶部热区为系统保留手势)。 + * 与左缘返回(SWIPE_LEFT)/ meow_ui_swipe_hint 互不影响——后者仍全屏生效,详见下方触摸机。 */ +#define MEOW_EV_NOTIF_PULL 5 + +/* 顶部下滑 → 通知中心判定:纵向 DOWN 手势的起点(origin_y)是否落在顶部热区内。 + * 热区由 meow_ui_statusbar_hotzone() 给出(非全屏=状态栏高、全屏=MEOW_SBAR_HOTZONE_FS、 + * 收合=提示线高)。供宿主单测直调(判据与触摸状态机共用)。 */ +int meow_ui_input_is_notif_pull(int origin_y, int hotzone) +{ + return origin_y >= 0 && origin_y < hotzone; +} + static int g_thread_started = 0; static pthread_t g_thread; static volatile int g_thread_run = 1; @@ -165,7 +179,14 @@ static void touch_release(void) } } else { int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP; - push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); + /* 顶部下滑 → 通知中心(系统手势,不下发 APP):起点在顶部热区内才认。 + * 热区高度随状态变化(非全屏=整条状态栏、全屏=MEOW_SBAR_HOTZONE_FS 缩小、 + * 收合=提示线高)——全屏 app 只在顶部极窄热区被系统占用,故「不影响全屏 app」。 */ + if (dir == MEOW_SWIPE_DOWN && + meow_ui_input_is_notif_pull(t_dy, meow_ui_statusbar_hotzone())) + push_ev(MEOW_EV_NOTIF_PULL, t_dy, ady > adx ? ady : adx); + else + push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx); } } else { push_ev(MEOW_EV_TAP, t_cx, t_cy); @@ -287,6 +308,11 @@ void meow_ui_run(const meow_ui_events *ev) } else if (e.a == MEOW_SWIPE_LEFT && sb_active) { meow_ui_request_exit(); } + } else if (e.type == MEOW_EV_NOTIF_PULL) { + if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ + /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 + * APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */ + meow_ui_sys_notif_pull(); } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { if (in_startup) continue; /* 启动窗口进度条静音 */ int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ diff --git a/meow_ui_sys.c b/meow_ui_sys.c index c271f5e..e21900e 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -156,18 +156,31 @@ void meow_ui_set_fullscreen(int on) int meow_ui_statusbar_height(void) { - return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; + if (!meow_ui_sys_statusbar_active()) return 0; + return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; } /* ---- 内部(gfx/input 用) ---- */ int meow_ui_sys_statusbar_active(void) { - return g_statusbar_on && !g_fullscreen; + /* collapsed 视为状态栏仍激活(只留提示线):与 fullscreen 并存(全屏启 collapsed)—— + * 这样 gfx.c present() 的收合动画块会运行(驱动 g_sbar_collapse_h 插值),且顶部提示线 + * 正常绘制/热区可用。fullscreen 且不 collapsed → 整条隐藏(沉浸,inset=0),保持既有语义。 */ + return g_statusbar_on && (meow_ui_statusbar_collapsed() || !g_fullscreen); } int meow_ui_sys_statusbar_height(void) { - return meow_ui_sys_statusbar_active() ? MUI_SBAR_H : 0; + if (!meow_ui_sys_statusbar_active()) return 0; + return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; +} + +void meow_ui_sys_notif_pull(void) +{ + /* 系统级「下拉展开通知中心」手势接线(M2.5x):输入层识别出「起始于顶部热区的 + * SWIPE_DOWN」后路由到此。通知中心 UI 为后续里程碑——此处先落记录(避免哑无痕); + * 届时由对应代理在该入口挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ + meow_log(MEOW_LOG_WARN, "notif_pull: notification center TBD (wired)"); } int meow_ui_sys_statusbar_force(void) diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 8caeb68..de80a7d 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -34,6 +34,13 @@ typedef struct meow_sys_state { #define MUI_SBAR_H 28 #define MUI_SBAR_FONT 20 +/* 收合(collapsed)状态下状态栏的有效加载高度(inset)= 顶部提示线高度(px)。 + * 收合=状态栏内容上移出屏、只留一条提示线:app 内容据此上移(非满 28), + * 形成「不完全沉浸(留提示线)」;与 fullscreen(整条隐藏、inset0)区别。 + * 取值沿用公共宏 MEOW_SBAR_HOTZONE_FS(=4,全屏时顶部热区/提示线的缩后高度), + * 具体绘制高度(gfx.c 的 MUI_SBAR_HINT_H=6)在渲染端,这里按提示线高度口径即可。 */ +#define MUI_SBAR_COLLAPSE_H MEOW_SBAR_HOTZONE_FS + /* 首次全量读取;失败仍返回 0(字段置 -1/0)。 */ int meow_ui_sys_init(void); @@ -43,12 +50,21 @@ void meow_ui_sys_refresh(void); /* 当前快照(内部只读)。 */ const meow_sys_state *meow_ui_sys_get(void); -/* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen)。 */ +/* 状态栏是否应绘制(= set_statusbar(1) 且 非 fullscreen),收合(collapsed)兼容: + * collapsed 视为状态栏仍在(只留提示线),故 collapsed 时即使 fullscreen 也返回 1, + * 让渲染端/输入层继续驱动上收动画与提示线;fullscreen 且不 collapsed → 0(整条隐藏, + * 沉浸,保持既有语义)。 */ int meow_ui_sys_statusbar_active(void); -/* 状态栏高度(px):active 时 28,否则 0(供 meow_ui_statusbar_height 用)。 */ +/* 状态栏有效高度(px,供 inset/提示条定位):active 时 28;collapsed 时=提示线高度 + * MUI_SBAR_COLLAPSE_H(非满 28,app 内容可上移);否则 0(供 meow_ui_statusbar_height 用)。 */ int meow_ui_sys_statusbar_height(void); +/* 系统级「下拉展开通知中心」手势挂钩(M2.5x 接线):输入层识别出「起始于顶部热区的 + * SWIPE_DOWN」后路由到这里。通知中心 UI 为后续里程碑,此入口作记录/占位;届时由对应 + * 代理在此挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ +void meow_ui_sys_notif_pull(void); + /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 * 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */ int meow_ui_sys_statusbar_force(void); From a8cd9fe5dce28876c96adf3fb63f813ab3725d10 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 13:02:17 +0800 Subject: [PATCH 05/49] =?UTF-8?q?=E9=80=9A=E7=9F=A5=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E8=A1=A5=E5=85=A8(agent-107:=20SDK=20icon+update=5Fprogress=20?= =?UTF-8?q?/=20IPC=20urgency+MSG=5FNOTIFY=5FPROGRESS=20/=20=E5=AD=98?= =?UTF-8?q?=E5=82=A8=20urgency+icon=E5=9B=9E=E9=80=80+progress=20/=20CSV?= =?UTF-8?q?=E5=8A=A03=E5=88=97)=20+=20=E5=85=A8=E5=B1=8F=E6=94=B6=E5=90=88?= =?UTF-8?q?=E8=A7=86=E8=A7=89+=E6=BB=91=E6=9D=A1(agent-108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_notify_ipc.c | 45 ++++++++++++++++++++++++++++++++++++--- meow_notify_ipc.h | 9 ++++++-- meow_ui.h | 10 +++++++-- meow_ui_gfx.c | 32 +++++++++++++++++----------- meow_ui_input.c | 8 +++++-- meow_ui_notify.c | 29 ++++++++++++++++++++++++- test_notify_integration.c | 28 +++++++++++++++++++++--- test_notify_ipc.c | 24 +++++++++++++++++++-- test_notify_post.c | 20 ++++++++++++++++- 9 files changed, 176 insertions(+), 29 deletions(-) diff --git a/meow_notify_ipc.c b/meow_notify_ipc.c index a4c768b..6486fed 100644 --- a/meow_notify_ipc.c +++ b/meow_notify_ipc.c @@ -6,13 +6,15 @@ * meow_ipc.h(同源一份、杜绝两端漂移): * - 帧头 16B:magic=0x4D4F5701 / version=1 / type / req_id / len(全小端 LE,mipsel 原生); * - MSG_NOTIFY(0x0020) ↑ 负载 = m_notify_t;MSG_NOTIFY_ACK(0x0021) ↓ 负载 = m_ack_t(status=nid); - * - MSG_NOTIFY_DISMISS(0x0022) ↑ 负载 = uint32 nid。 + * - MSG_NOTIFY_DISMISS(0x0022) ↑ 负载 = uint32 nid; + * - MSG_NOTIFY_PROGRESS(0x0024) ↑ 负载 = m_notify_progress_t(nid+progress,实时进度仅内存)。 * * 设计要点: * - 只做「一次性 connect + send + read ACK」,不维护连接/订阅循环(与 meow_ipc.c 客户端同型); * - 映射层(SDK 枚举 ↔ IPC 字段)集中在本文件:分类枚举 CAT_* ↔ 字符串 * "general"/"message"/"email"/"event"/"progress"/"media";timeout_ms → expire_timeout: * >0 该毫秒、0=不自动过期、-1(ONGOING)=0xFFFFFFFF(并置 ongoing=1);媒体类型置 media=1; + * SDK `icon` → app_icon(空则 launcher 按 app_id 回退 app 图标);urgency 0-3 直通(越界钳位 NORMAL); * - 优雅降级:无 socket(connect 失败)/写失败/ACK 超时(SO_RCVTIMEO)/非 ACK 应答 → * 返回 0(调用方 meow_ui_notify.c 保留本地 id 兜底),绝不崩; * - 线程安全:app_id 经 pthread_once 缓存;socket 路径每次调用读 env MEOW_IPC_SOCK 覆盖 @@ -82,8 +84,8 @@ static uint32_t timeout_to_expire(int timeout_ms) return (uint32_t)timeout_ms; /* 0(NEVER)/ >0(毫秒) */ } -/* 构建 IPC 负载 m_notify_t(app_icon/actions/hints v1 留空,M2b D-Bus 桥再填)。 - * 仅在持有调用者互斥之外、发送前调用(纯 CPU 拷贝,不阻塞)。 */ +/* 构建 IPC 负载 m_notify_t(app_icon<->SDK icon;urgency 0-3;actions/hints v1 留空, + * M2b D-Bus 桥再填)。仅在持有调用者互斥之外、发送前调用(纯 CPU 拷贝,不阻塞)。 */ static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_t *mn) { memset(mn, 0, sizeof *mn); @@ -91,6 +93,7 @@ static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_ snprintf(mn->category, sizeof mn->category, "%s", cat_to_str(n->cat)); snprintf(mn->summary, sizeof mn->summary, "%s", n->title ? n->title : ""); snprintf(mn->body, sizeof mn->body, "%s", n->body ? n->body : ""); + snprintf(mn->app_icon, sizeof mn->app_icon, "%s", n->icon ? n->icon : ""); snprintf(mn->target_activity, sizeof mn->target_activity, "%s", n->target_activity ? n->target_activity : ""); mn->replaces_id = replaces_id; /* 0=新建;>0=更新(不存在则落为新建,见 launcher) */ @@ -98,6 +101,9 @@ static void build_m_notify(const meow_notify *n, uint32_t replaces_id, m_notify_ mn->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); mn->media = (n->cat == MEOW_NOTIFY_CAT_MEDIA) ? 1 : 0; mn->ongoing = (n->timeout_ms == MEOW_NOTIFY_TIMEOUT_ONGOING) ? 1 : 0; + /* urgency:越界钳位到 NORMAL(缺省);0-3 直通(launcher 侧再回退缺省) */ + mn->urgency = ((int)n->urgency >= 0 && (int)n->urgency < (int)MEOW_NOTIFY_URG_COUNT) + ? (uint8_t)n->urgency : (uint8_t)MEOW_NOTIFY_URG_NORMAL; } /* ============================================================ */ @@ -241,3 +247,36 @@ void meow_notify_ipc_dismiss(uint32_t nid) } close(fd); } + +void meow_notify_ipc_update_progress(uint32_t nid, int progress) +{ + int fd; + if (nid == 0) return; + m_notify_progress_t p; + memset(&p, 0, sizeof p); + p.nid = nid; + p.progress = (progress < -1) ? -1 : (progress > 100 ? 100 : progress); + fd = conn_path(ipc_sock_path()); + if (fd < 0) return; /* 无 socket:静默 */ + set_ack_timeout(fd); + if (!send_frame(fd, MSG_NOTIFY_PROGRESS, 0x4E4F5450u /* "NOTP" */, &p, (uint32_t)sizeof p)) { + close(fd); /* 写失败:静默 */ + return; + } + /* 读回应答(launcher 对 PROGRESS 回 MSG_ACK,best-effort;读回确保 launcher 已处理完) */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + uint32_t rmagic, rlen; + uint16_t rver, rtype; + memcpy(&rmagic, rhdr + 0, 4); + memcpy(&rver, rhdr + 4, 2); + memcpy(&rtype, rhdr + 6, 2); + memcpy(&rlen, rhdr + 12, 4); + if (rmagic == MEOW_IPC_MAGIC && rver == MEOW_IPC_VERSION && + rtype == MSG_ACK && rlen >= sizeof(m_ack_t)) { + uint8_t pay[sizeof(m_ack_t)]; + (void)read_all(fd, pay, sizeof(m_ack_t)); /* 读到即确认;内容不关心 */ + } + } + close(fd); +} diff --git a/meow_notify_ipc.h b/meow_notify_ipc.h index c1da243..f02a5a0 100644 --- a/meow_notify_ipc.h +++ b/meow_notify_ipc.h @@ -4,9 +4,10 @@ * 定位(任务 #5 SDK↔IPC 桥关键断点):让框架 app 调 meow_notify_send() 的通知真正经 * AF_UNIX IPC 送到 launcher(meow_launcherd)中央存储(meow_notif.c 的 meow_notif_post), * 通知中心(阶段 B · M5)才能显示。消息通道(FRAMEWORK_DESIGN §1 / meow_ipc.h): - * - MSG_NOTIFY 0x0020 ↑:推送/更新通知(负载 m_notify_t); + * - MSG_NOTIFY 0x0020 ↑:推送/更新通知(负载 m_notify_t;含 SDK icon→app_icon、urgency); * - MSG_NOTIFY_ACK 0x0021 ↓:落库结果(m_ack_t,status=launcher 分配的通知 id nid); - * - MSG_NOTIFY_DISMISS 0x0022 ↑:撤下通知(负载 uint32 nid)。 + * - MSG_NOTIFY_DISMISS 0x0022 ↑:撤下通知(负载 uint32 nid); + * - MSG_NOTIFY_PROGRESS 0x0024 ↑:实时更新通知进度(负载 m_notify_progress_t,仅改内存活跃槽)。 * * 约定:只做「一次性发送 + 读 ACK」,不造 IPC 订阅循环;SDK 本地活动表(meow_ui_notify.c) * 保留(app 进程内即时查询/配额自检),真正的通知中心数据在 launcher 中央存储,二者以 @@ -38,6 +39,10 @@ uint32_t meow_notify_ipc_send(const meow_notify *n, uint32_t replaces_id); * 静默(不崩、不回读 ACK)。 */ void meow_notify_ipc_dismiss(uint32_t nid); +/* 一次性发送 MSG_NOTIFY_PROGRESS 更新 launcher 侧已存在通知的进度(仅改内存活跃槽, + * 不落盘)。best-effort:写失败/无 socket 静默。 */ +void meow_notify_ipc_update_progress(uint32_t nid, int progress); + #ifdef __cplusplus } #endif diff --git a/meow_ui.h b/meow_ui.h index 298e379..3614006 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -297,12 +297,13 @@ typedef enum { } meow_notify_urgency; /* 发送负载(对齐 freedesktop Notify() 字段;SDK 内部复制副本,不持 app 外部指针, - * app 只需保证 title/body/target_activity 指针在调用返回前有效)。 */ + * app 只需保证 icon/title/body/target_activity 指针在调用返回前有效)。 */ typedef struct meow_notify { meow_notify_category cat; /* 类型(未声明则按 4 种上限自动注册) */ - meow_notify_urgency urgency; /* 紧急度 */ + meow_notify_urgency urgency; /* 紧急度(缺省 MEOW_NOTIFY_URG_NORMAL;越界钳位) */ const char *title; /* summary:标题,单行(必填,NULL 则发送失败) */ const char *body; /* body:正文,可多行(可 NULL) */ + const char *icon; /* app_icon:图标 icon name 或 path;NULL/空 = 用该 app 默认图标(launcher 按 app_id 回退) */ int timeout_ms; /* 见上超时语义;推荐 MEOW_NOTIFY_TIMEOUT_* */ const char *target_activity; /* 通知中心点击跳转的 Activity 名(可 NULL;M3 导航预留) */ int progress; /* 进度 0-100;-1=不显示进度条(缺省 -1) */ @@ -318,6 +319,11 @@ uint32_t meow_notify_send(const meow_notify *n); /* 取消一条通知。1=成功;0=未找到(id 无效/已过期/已取消)。 */ int meow_notify_cancel(uint32_t id); +/* 实时更新一条活动通知的进度(0-100,-1=清除进度条)。local_id=meow_notify_send 的返回值。 + * 语义(用户定案):仅改 SDK 内部活动槽 + 经 IPC 通知 launcher 改**内存活跃槽**;不落盘—— + * TF 快照只在「创建时 / 结束(100% 或 dismiss)」触发。返回 1=找到并更新;0=未找到/非法。 */ +int meow_notify_update_progress(uint32_t local_id, int progress); + /* 查询当前活动(未过期、未取消)通知数/是否还有。 */ int meow_notify_active_count(void); int meow_notify_count_in_type(meow_notify_category cat); diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index d6a9798..e7841af 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -67,8 +67,9 @@ static char *g_dump_path = NULL; * 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) */ + * 过渡(每帧插值)留待后续代理或经 meow_ui_statusbar_animate(ms) 启用的简易线性逼近。 + * 「收合=只留提示线」占高 = MUI_SBAR_COLLAPSE_H(=MEOW_SBAR_HOTZONE_FS=4,meow_ui_sys.h), + * 与 inset(meow_ui_statusbar_height())/顶部下滑热区/收合渲染同口径——replaced MUI_SBAR_HINT_H。 */ 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 设置 */ @@ -357,7 +358,8 @@ void meow_ui_swipe_hint(int x) if (!g_init_ok) return; const meow_theme *th = meow_theme_get(); /* 进度条画在状态栏下沿(z 在状态栏之下,不被全局状态栏覆盖)。 - * 有状态栏(非全屏)→ y = 状态栏高度;无状态栏(全屏/隐藏)→ y=0。 */ + * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 + * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); if (x > 0) { if (x > g_xres) x = g_xres; @@ -544,15 +546,16 @@ static int statusbar_collapse_in_transition(void) /* 上收状态栏:内容整体上移出屏(不画时钟/电量),仅在顶部画一条「提示线」。 * 与 meow_ui_swipe_hint(左滑返回进度条,5px,动态)不同:此线是「状态栏可下拉还原」的 - * 常驻提示(主题强调色下边缘)。原状态栏整块清回主题背景,避免残影(app inset 之内)。 */ + * 常驻提示(主题强调色下边缘)。收合只占/清「提示线」高度区域(MUI_SBAR_COLLAPSE_H=4), + * 不再清整 MUI_SBAR_H(28) 行 —— app 内容按 inset=提示线高可上移到提示线下缘,无 28px 空条。 */ 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); + /* h 越大内容越贴顶;基座:折叠态清「提示线」区域底 + 顶部提示线(提示线下缘=主题 accent) */ + meow_ui_fill_rect(0, 0, w, MUI_SBAR_COLLAPSE_H, th->bg); + meow_ui_fill_rect(0, MUI_SBAR_COLLAPSE_H - 1, w, 1, th->accent); } /* ---- 公共 API(基座实现;动画插值骨架见 statusbar_collapse_step)---- */ @@ -576,9 +579,10 @@ void meow_ui_statusbar_animate(int ms) int meow_ui_statusbar_hotzone(void) { /* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 - * 上收(collapsed、非全屏)时热区=提示线下缘高度。全屏态由 meow_ui_sys 维护。 */ + * 上收(collapsed、非全屏)时热区=提示线下缘高度(MUI_SBAR_COLLAPSE_H,与渲染/inset 同口径)。 + * 全屏态由 meow_ui_sys 维护。 */ if (meow_ui_sys_statusbar_active()) - return g_sbar_collapsed ? MUI_SBAR_HINT_H : MUI_SBAR_H; + return g_sbar_collapsed ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; return MEOW_SBAR_HOTZONE_FS; } @@ -602,10 +606,12 @@ 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)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部 MUI_SBAR_H 行。 - * 全屏/隐藏时 active()==0 不画(沉浸),app 内容可占满整屏。触发条件: - * 数据已变(1s 轻刷/显隐切换) / 顶条被 app 内容触及 / 上收动画未到位(逐帧推进)。 - * 展开态仍走 meow_ui_sys_statusbar_render()(meow_ui_sys.c 不改,留给后续代理); + /* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。 + * 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行), + * app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0 + * 不画(沉浸),app 内容可占满整屏。触发条件:数据已变(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() || diff --git a/meow_ui_input.c b/meow_ui_input.c index 7a155d2..8c7d0b9 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -317,8 +317,12 @@ void meow_ui_run(const meow_ui_events *ev) if (in_startup) continue; /* 启动窗口进度条静音 */ int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ /* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。 - * 全屏/沉浸不画系统条(无返回手势);APP 若注册 on_swipe_progress 仍可自绘。 */ - if (meow_ui_sys_statusbar_active()) meow_ui_swipe_hint(xc); + * M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint) + * 仍需绘制——不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏 + * 时滑条完全不显示。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、 + * 全屏=0、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加。 + * APP 若注册 on_swipe_progress 仍可自绘(系统条照常画)。 */ + meow_ui_swipe_hint(xc); if (g_events_set && g_events.on_swipe_progress) g_events.on_swipe_progress(x0, dx); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); diff --git a/meow_ui_notify.c b/meow_ui_notify.c index dbad728..489ab52 100644 --- a/meow_ui_notify.c +++ b/meow_ui_notify.c @@ -35,7 +35,10 @@ /* ---- 内部活动表尺寸(配额见 meow_ui.h) ---- */ #define MEOW_NOTIFY_MAX_ACTIVE (MEOW_NOTIFY_MAX_TYPES * MEOW_NOTIFY_MAX_PER_TYPE) -/* 活动通知槽(内部;标题/正文等复制副本,不持 app 外部指针,避免栈字符串悬垂) */ +/* 活动槽内 icon 缓冲上限(与 IPC app_icon[48] 一致;SDK 复制副本,不持 app 外部指针) */ +#define MEOW_SLOT_ICON_MAX 48 + +/* 活动通知槽(内部;标题/正文/图标等复制副本,不持 app 外部指针,避免栈字符串悬垂) */ typedef struct { uint32_t id; /* 本地唯一 id(app 侧句柄);0=空闲槽 */ uint32_t ipc_id; /* launcher 中央存储分配的通知 id(nid);0=未同步/降级 */ @@ -47,6 +50,7 @@ typedef struct { char title[64]; char body[128]; char target_activity[32]; + char icon[MEOW_SLOT_ICON_MAX]; /* 图标 name/path;空=用 app 默认图标(launcher 回退) */ int progress; /* 0-100;-1=无进度条 */ } notify_slot_t; @@ -111,6 +115,7 @@ static void notify_fill(notify_slot_t *s, const meow_notify *n, uint32_t id, uin snprintf(s->body, sizeof s->body, "%s", n->body ? n->body : ""); snprintf(s->target_activity, sizeof s->target_activity, "%s", n->target_activity ? n->target_activity : ""); + snprintf(s->icon, sizeof s->icon, "%s", n->icon ? n->icon : ""); s->progress = (n->progress < -1) ? -1 : (n->progress > 100 ? 100 : n->progress); } @@ -253,6 +258,28 @@ int meow_notify_cancel(uint32_t id) return rc; } +int meow_notify_update_progress(uint32_t local_id, int progress) +{ + int p, i, found = 0; + uint32_t ipc_id = 0; + if (local_id == 0) return 0; + p = (progress < -1) ? -1 : (progress > 100 ? 100 : progress); /* 钳位 [-1,100] */ + pthread_mutex_lock(&g_mu); + notify_purge_expired(); + for (i = 0; i < MEOW_NOTIFY_MAX_ACTIVE; i++) { + if (g_slots[i].active && g_slots[i].id == local_id) { + g_slots[i].progress = p; /* 实时:仅改内存活动槽,不落盘 */ + ipc_id = g_slots[i].ipc_id; /* launcher 侧 nid;0=未同步/降级 */ + found = 1; + break; + } + } + pthread_mutex_unlock(&g_mu); + /* 落点(best-effort):经 IPC 通知 launcher 改其内存活跃槽;无 launcher/未同步则静默。 */ + if (found && ipc_id) meow_notify_ipc_update_progress(ipc_id, p); + return found; +} + int meow_notify_active_count(void) { int i, n = 0; diff --git a/test_notify_integration.c b/test_notify_integration.c index 6ea53d9..2f50f0a 100644 --- a/test_notify_integration.c +++ b/test_notify_integration.c @@ -41,7 +41,7 @@ uint64_t meow_ui_time_ms(void) /* ---- stub server 捕获(每次连接一帧) ---- */ #define MAXCAP 16 -typedef struct { int type; m_notify_t n; uint32_t dismiss_id; uint32_t nid; } srec_t; +typedef struct { int type; m_notify_t n; m_notify_progress_t prog; uint32_t dismiss_id; uint32_t nid; } srec_t; static srec_t g_rec[MAXCAP]; static int g_nrec = 0; static int g_stop = 0; @@ -77,6 +77,7 @@ static void *server(void *arg) memset(&g_rec[idx], 0, sizeof g_rec[idx]); g_rec[idx].type = (int)type; if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_rec[idx].n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_PROGRESS && len >= sizeof(m_notify_progress_t)) memcpy(&g_rec[idx].prog, pay, sizeof(m_notify_progress_t)); if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_rec[idx].dismiss_id, pay, 4); /* 分配 nid = 100 + NOTIFY 序号 */ uint32_t seq = 0; @@ -85,7 +86,7 @@ static void *server(void *arg) uint32_t ack_nid = g_rec[idx].nid; pthread_mutex_unlock(&g_mu); - if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS || type == MSG_NOTIFY_PROGRESS) { m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = ack_nid; uint8_t rhdr[MEOW_IPC_HDR_LEN]; uint32_t mag = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION, t = (uint16_t) @@ -140,11 +141,13 @@ int main(void) /* S1 通用通知 */ meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.urgency = MEOW_NOTIFY_URG_NORMAL; - nt.title = "t1"; nt.timeout_ms = 0; + nt.title = "t1"; nt.timeout_ms = 0; nt.icon = "os.meow.hello/icon.png"; uint32_t id1 = meow_notify_send(&nt); check("S1 send 返回本地 id=1", id1 == 1); check("S1 active_count=1", meow_notify_active_count() == 1); check("S1 stub 回 nid=101", rec_at(0).nid == 101); + check("S1 映射 urgency=NORMAL(1)", rec_at(0).n.urgency == MEOW_NOTIFY_URG_NORMAL); + check("S1 映射 icon→app_icon", !strcmp(rec_at(0).n.app_icon, "os.meow.hello/icon.png")); /* S2 媒体通知:单例 + media=1 */ memset(&nt, 0, sizeof nt); @@ -182,6 +185,25 @@ int main(void) 触发丢最旧的是 S5 循环第 4 次 send = 第 7 条 NOTIFY(rec[7]),其 replaces_id==103。 */ check("S5 丢最旧后 send replaces_id=被丢nid(103)", rec_at(7).n.replaces_id == 103); + /* S5b update_progress:新发一条 progress → 本地 id → update → stub 捕获 MSG_NOTIFY_PROGRESS */ + memset(&nt, 0, sizeof nt); + nt.cat = MEOW_NOTIFY_CAT_PROGRESS; nt.title = "prog"; nt.timeout_ms = 0; + nt.progress = 10; + uint32_t idp = meow_notify_send(&nt); + int ni = -1; + for (int i = 0; i < MAXCAP; i++) { + srec_t r = rec_at(i); + if (r.type == MSG_NOTIFY && !strcmp(r.n.summary, "prog")) { ni = i; break; } + } + check("S5b 找到 prog 通知帧", ni >= 0); + int rc_up = meow_notify_update_progress(idp, 70); + check("S5b update_progress 返回 1", rc_up == 1); + srec_t pr = rec_at(ni + 1); + check("S5b stub 捕获 MSG_NOTIFY_PROGRESS 帧", pr.type == MSG_NOTIFY_PROGRESS); + check("S5b progress nid=该通知 launcher nid", ni >= 0 && pr.prog.nid == rec_at(ni).nid); + check("S5b progress 载=70", pr.prog.progress == 70); + check("S5b update_progress(未知 id) 返回 0", meow_notify_update_progress(0xFFFFFF, 50) == 0); + stop_server(); pthread_join(th, NULL); diff --git a/test_notify_ipc.c b/test_notify_ipc.c index f1ba227..c7f5dc3 100644 --- a/test_notify_ipc.c +++ b/test_notify_ipc.c @@ -30,6 +30,7 @@ typedef struct { uint32_t type; /* 收到的帧类型 */ m_notify_t n; /* MSG_NOTIFY 负载 */ + m_notify_progress_t prog; /* MSG_NOTIFY_PROGRESS 负载 */ uint32_t dismiss_id; /* MSG_NOTIFY_DISMISS 负载 */ uint32_t req_id; uint32_t nid_to_return; /* ACK 中回填的 status */ @@ -47,6 +48,7 @@ static void cap_store(uint32_t type, uint32_t req_id, const uint8_t *pay, uint32 g_cap.req_id = req_id; g_cap.nid_to_return = nid_to_return; if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_cap.n, pay, sizeof(m_notify_t)); + if (type == MSG_NOTIFY_PROGRESS && len >= sizeof(m_notify_progress_t)) memcpy(&g_cap.prog, pay, sizeof(m_notify_progress_t)); if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_cap.dismiss_id, pay, 4); g_cap.got = 1; pthread_mutex_unlock(&g_cap_mu); @@ -87,7 +89,7 @@ static void *server_thread(void *arg) /* MSG_NOTIFY → 回 m_ack_t(code=OK, status=nid_to_return); * MSG_NOTIFY_DISMISS → 回 m_ack_t(code=OK, status=0,同 launcher 行为)。 */ - if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS) { + if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS || type == MSG_NOTIFY_PROGRESS) { m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; @@ -152,9 +154,10 @@ int main(void) meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_PROGRESS; - nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.urgency = MEOW_NOTIFY_URG_HIGH; nt.title = "下载中"; nt.body = "70%"; + nt.icon = "os.meow.hello/icon.png"; nt.timeout_ms = 5000; /* >0 → expire=5000, ongoing=0 */ nt.progress = 150; /* 钳位 → 100 */ uint32_t nid = meow_notify_ipc_send(&nt, 0); @@ -175,6 +178,8 @@ int main(void) check("T2 replaces_id 透传=0", cn->replaces_id == 0); check("T1 app_id=testapp", !strcmp(cn->app_id, "testapp")); check("T1 summary=下载中", !strcmp(cn->summary, "下载中")); + check("T1 urgency(HIGH)→wire urgency=2", cn->urgency == MEOW_NOTIFY_URG_HIGH); + check("T1 icon→app_icon", !strcmp(cn->app_icon, "os.meow.hello/icon.png")); } /* ---------- T1/T2:媒体类型 + ONGOING(-1) ---------- */ @@ -275,6 +280,21 @@ int main(void) check("T5 dismiss 负载=nid=0xABCD", g_cap.dismiss_id == 0xABCDu); } + /* ---------- T6:update_progress 发 MSG_NOTIFY_PROGRESS(nid + progress 钳位)---------- */ + { + char sock[108]; + g_listen_fd = arb_listen(sock, sizeof sock); + setenv("MEOW_IPC_SOCK", sock, 1); + pthread_t th; + pthread_create(&th, NULL, server_thread, NULL); + meow_notify_ipc_update_progress(0x1234, 250); /* 钳位 250→100 */ + pthread_join(th, NULL); + close(g_listen_fd); + check("T6 stub 收到 MSG_NOTIFY_PROGRESS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_PROGRESS); + check("T6 progress 负载 nid=0x1234", g_cap.prog.nid == 0x1234); + check("T6 progress 钳位 250→100", g_cap.prog.progress == 100); + } + printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; diff --git a/test_notify_post.c b/test_notify_post.c index beac070..b3d0440 100644 --- a/test_notify_post.c +++ b/test_notify_post.c @@ -49,6 +49,7 @@ static void launcher_map(const m_notify_t *mn, meow_notif_t *n) snprintf(n->target_activity, sizeof n->target_activity, "%s", mn->target_activity); n->expire_timeout = mn->expire_timeout; n->progress = mn->progress; + n->urgency = mn->urgency; /* 0-3;旧调用=0 → LOW(静默) */ n->ongoing = mn->ongoing; n->media = mn->media; } @@ -59,6 +60,15 @@ static void launcher_map(const m_notify_t *mn, meow_notif_t *n) static int g_listen_fd = -1; static int g_stop = 0; static uint32_t g_posted_nid = 0; +static meow_notif_t g_stored; /* 回读 launcher 存储的最近一条通知(store_cb) */ +static int g_store_called = 0; + +static int store_cb(const meow_notif_t *n, void *ctx) +{ + (void)ctx; + if (n) { g_stored = *n; g_store_called = 1; } + return 1; /* 只取第一条 */ +} static void reply_ack(int c, uint16_t type, uint32_t req_id, uint32_t status) { @@ -130,13 +140,21 @@ int main(void) /* SDK send → launcher 真存储。media + ONGOING 走全部映射分支。 */ meow_notify nt; memset(&nt, 0, sizeof nt); - nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_NORMAL; + nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_HIGH; nt.title = "正在播放"; nt.timeout_ms = -1; nt.progress = 40; + nt.icon = "os.meow.hello/icon.png"; uint32_t nid = meow_notify_ipc_send(&nt, 0); check("send 经 launcher 真存储回 nid>0", nid != 0); check("send 拿到 launcher 分配的 nid(与 post 一致)", g_posted_nid == nid); check("launcher 存储 count(media app)=1", meow_notif_count("intgapp") == 1); + /* 回读 launcher 存储,验证 urgency/icon/progress 全链路落库 */ + g_store_called = 0; memset(&g_stored, 0, sizeof g_stored); + meow_notif_list(NULL, store_cb, NULL); + check("post 存储 urgency=HIGH(2)", g_store_called && g_stored.urgency == MEOW_NOTIFY_URG_HIGH); + check("post 存储 icon→app_icon", g_stored.app_icon[0] && !strcmp(g_stored.app_icon, "os.meow.hello/icon.png")); + check("post 存储 progress=40", g_stored.progress == 40); + /* SDK dismiss → launcher 撤下 */ meow_notify_ipc_dismiss(nid); check("dismiss 后 launcher 存储 count=0", meow_notif_count("intgapp") == 0); From b8c8cd1b210455964fa9e601501f63feff7aef23 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 13:42:41 +0800 Subject: [PATCH 06/49] =?UTF-8?q?=E5=AD=97=E9=87=8D=E7=BB=93=E6=9E=84+fami?= =?UTF-8?q?ly=E2=86=92=E5=AD=97=E9=87=8D=E5=AD=90=E8=8F=9C=E5=8D=95(agent-?= =?UTF-8?q?111:=20fonts///,=20gen=5Ffont.py=20--weight,=20?= =?UTF-8?q?=E4=B8=A4=E7=BA=A7=E6=89=AB=E6=8F=8F,=20s=5Ffont=5Fdropdown?= =?UTF-8?q?=E5=AD=90=E8=8F=9C=E5=8D=95)=20+=20=E5=85=A8=E5=B1=8F=E5=B7=A6?= =?UTF-8?q?=E6=BB=91=E6=BB=91=E6=9D=A1=E4=BF=AE=E5=A4=8D(agent-112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui_gfx.c | 33 ++++++++++++++-------------- meow_ui_input.c | 57 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index e7841af..082d937 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -29,12 +29,12 @@ #define FBIO_WAITFORVSYNC 0x4620 #define FB_DEV "/dev/fb0" -#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.png" -#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/font.idx" +#define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png" +#define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx" #define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" -/* 默认字库(卡 MeowOS/fonts/default)与固件内置之外,launcher 会把「当前生效字体」 - * 经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): +/* 默认字库(卡 MeowOS/fonts/default/regular,M2.6 字重结构)与固件内置之外,launcher 会把 + * 「当前生效字体」经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): * MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = 或 "default"。 * SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */ @@ -361,18 +361,19 @@ void meow_ui_swipe_hint(int x) * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); - if (x > 0) { - if (x > g_xres) x = g_xres; - /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, - * 否则条只增不减(用户要求:右滑变长、左滑回缩实时可见)。 */ - if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); - meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->accent); - g_hint_last = x; - } else if (g_hint_last > 0) { - /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ - meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); - g_hint_last = 0; + /* x<0 视为清空(条长 0);条长上限=屏宽。 */ + if (x < 0) x = 0; + if (x > g_xres) x = g_xres; + /* 只按「新旧长度的差」增量绘制,避免整段重涂背景色: + * - 伸长:仅补画新增段(旧段已是 accent,不再用 bg 重涂——否则 app 底色≠theme bg + * 时会在旧段留下 bg 色块,就是残留残条); + * - 回缩/清空:仅把「超出新长度」的尾部填回背景色(实时回缩)。 */ + if (x > g_hint_last) { + meow_ui_fill_rect(g_hint_last, hy, x - g_hint_last, MUI_HINT_H, th->accent); + } else if (x < g_hint_last) { + meow_ui_fill_rect(x, hy, g_hint_last - x, MUI_HINT_H, th->bg); } + g_hint_last = x; /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ } @@ -704,7 +705,7 @@ int meow_font_set_tier(int tier) /* 字库加载优先级(M2.x): * 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file) * —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file; - * 2) 卡默认 MeowOS/fonts/default/font.png(不再硬编码 MeowAPPs/fonts); + * 2) 卡默认 MeowOS/fonts/default/regular/font.png(M2.6 字重结构;不再硬编码 MeowAPPs/fonts); * 3) 固件 /usr/share/meowos/font/font.png。 * 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */ static int load_font(void) diff --git a/meow_ui_input.c b/meow_ui_input.c index 8c7d0b9..c964f8c 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -125,6 +125,7 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ +static int t_gmax_dx = 0; /* 本手势「最大右移位移」(t_cx-t_dx 的最大值,条长上限) */ /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -136,6 +137,7 @@ static void touch_press(int x, int y) t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; + t_gmax_dx = 0; /* 新手势:最大右移位移清零(条长从 0 计) */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ static void touch_update(int x, int y) @@ -145,13 +147,17 @@ static void touch_update(int x, int y) } if (x >= 0) t_cx = x; if (y >= 0) t_cy = y; + if (t_down && t_origin_valid) { + int dx = t_cx - t_dx; + if (dx > t_gmax_dx) t_gmax_dx = dx; /* 记录本手势已达的最大右移位移(条长上限) */ + } } static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; @@ -163,7 +169,8 @@ static void touch_release(void) * 被手势级清空,绝不继承上一手势坐标,确保: * ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W); * ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」; - * ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。 + * ③ 返回判定要求右移严格正向(t_cx-t_dx>=0 且 ≥SWIPE_THRESH),杜绝「向左划也误返回」; + * 左划过程仅让进度条实时回缩,不额外生长(过程见上方 MEOW_EV_SWIPE_PROGRESS)。 * SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。 * * 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP, @@ -192,7 +199,7 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; } /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ @@ -224,12 +231,22 @@ static void *input_thread(void *arg) if (code == ABS_X || code == ABS_MT_POSITION_X) { if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */ else t_cx = val; - /* 左滑进度实时上报(视觉条):原点确实在左缘(t_dxt_dx)。抬手触发仍由 touch_release 判定;这里只报过程。 - * t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已 - * 清空,杜绝「中部点一下出条 / 向左划也出条」。 */ - if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx) - push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx); + /* 左滑进度实时上报(视觉条)。抬手触发仍由 touch_release 判定;这里只报过程。 + * 修复要点(左缘返回滑条 bug): + * ① 双向上报(不再要求 t_cx>t_dx):向右→条伸长、向左→条实时回缩, + * 摒弃「只在返回瞬间闪末帧 / 反向才出条」; + * ② 仅左缘起手(t_dx= 0 && t_dx < EDGE_W) { + int dx = t_cx - t_dx; + if (dx > t_gmax_dx) t_gmax_dx = dx; + int len = dx < 0 ? 0 : dx; + if (len > t_gmax_dx) len = t_gmax_dx; + push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, len); + } } else if (code == ABS_Y || code == ABS_MT_POSITION_Y) { if (t_down) touch_update(t_cx, val); else t_cy = val; @@ -282,9 +299,12 @@ void meow_ui_run(const meow_ui_events *ev) meow_input_ev e; int got = 0; while (meow_input_pop(&e)) { + if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */ got = 1; int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */ if (e.type == MEOW_EV_TAP) { + if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起) + * 在进入瞬间命中 UI 按钮(如全屏按钮)会误触发——丢弃。 */ /* 抬手未达标:清视觉条 */ meow_ui_swipe_hint(-1); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); @@ -315,16 +335,17 @@ void meow_ui_run(const meow_ui_events *ev) meow_ui_sys_notif_pull(); } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { if (in_startup) continue; /* 启动窗口进度条静音 */ - int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ - /* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。 - * M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint) - * 仍需绘制——不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏 - * 时滑条完全不显示。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、 - * 全屏=0、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加。 - * APP 若注册 on_swipe_progress 仍可自绘(系统条照常画)。 */ - meow_ui_swipe_hint(xc); + /* e.a=按下X(t_dx),e.b=条长 len(输入层已 clamp 到「本手势最大右移位移」且≥0)。 + * SDK 自动画顶部条:从 x=0 画到 len(右移位移),向右伸长、向左实时回缩。 + * M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条仍需绘制——不再以 + * meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏时滑条完全不显示。 + * 条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、全屏=0、收合=提示线下缘 + * (MUI_SBAR_COLLAPSE_H),与提示线不重叠加。APP 若注册 on_swipe_progress 仍可自绘 + * (系统条照常画);递交给 app 的是 (len-x_start) 使 x_start+dx==len,与系统条同长。 */ + int len = e.b; + meow_ui_swipe_hint(len); if (g_events_set && g_events.on_swipe_progress) - g_events.on_swipe_progress(x0, dx); + g_events.on_swipe_progress(e.a, len - e.a); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); } } From 2e04b561955d72a8cb3600661ef9e716e02cb394 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 14:14:30 +0800 Subject: [PATCH 07/49] =?UTF-8?q?=E5=85=A8=E5=B1=8F=E6=BB=91=E6=9D=A1?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=B7=AF=E5=BE=84+=E5=AF=B9=E6=AF=94?= =?UTF-8?q?=E8=89=B2(112/115)=20+=20=E5=AD=97=E4=BD=93=E7=B4=A2=E5=BC=95+?= =?UTF-8?q?=E6=9F=A5=E5=88=97=E8=A1=A8+=E4=B8=A2=E5=A4=B1=E9=99=8D?= =?UTF-8?q?=E7=BA=A7(113)=20+=20=E6=96=87=E6=A1=A3=E5=88=B7=E6=96=B0(114)?= =?UTF-8?q?=20+=20svcd=20CLI=E6=96=87=E6=A1=A3+mips-repo=E6=8B=86bins/mpk(?= =?UTF-8?q?116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui.h | 35 ++++++++++++++++++++++++++++++ meow_ui_gfx.c | 27 ++++++++++++----------- meow_ui_input.c | 57 ++++++++++++++++++------------------------------- 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index 3614006..e7372c3 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -333,6 +333,41 @@ 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); +/* ============================================================ */ +/* §5.7 应用可读 TF 卡字体列表(M2.58:字体索引 / 查列表 / 丢失降级默认) */ +/* ============================================================ */ +/* 系统字体管理器的面向应用接口(规格 §5.7.1/§5.7.2): + * - 权威列表 = launcher 扫描 MeowOS/fonts/// 后落盘的持久文件 + * MeowOS/config/fonts/index(CSV:family,weight,path,label,tier,glyph_count)。 + * app 只经本接口读该列表(不直接拼路径、不依赖 launcher 内存数组)。 + * - 渲染仍走 env MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」, + * 选中字体那一套);本节的 get_available_count/enumerate 只负责「列有哪些库」, + * select_by_id 负责「app 会话内固定用哪套」。两路明确。 + * - 无卡 / 无 index → 仅 1 项板载默认(family=default)。 */ +#ifndef MEOW_FONT_INFO_DEFINED +#define MEOW_FONT_INFO_DEFINED +typedef struct meow_font_info { + char family[64]; /* family slug = MeowOS/fonts// 一级目录名 */ + char name[64]; /* 显示名(index 无单列 → 回退 family) */ + char weight[32]; /* weight slug(regular|bold|…,缺省 regular) */ + char tier[8]; /* 档位 common|gbk|full(info 用) */ + int glyph_count; /* font.json glyph_count(info 用) */ + char label[128]; /* index 显示名(含字重 + 尺寸标记) */ + int size_list[16]; /* 原生字号表(index 无 → 空) */ + int size_count; /* 有效字号数 */ + int bit_depth; /* 4|8(index 无 → 0) */ + char path_png[320]; /* 卡根相对 MeowOS/fonts///font.png */ +} meow_font_info; +#endif + +/* 可用字库数(含默认;无卡 → 1)。 */ +int meow_font_get_available_count(void); +/* 按序号取:idx=0=默认行(family=default);越界返回 0。 */ +int meow_font_enumerate(int idx, meow_font_info *out); +/* 选该套为 app 会话默认字体:id=0 跟随系统默认(font.file,经 env);id>0 固定用该套。 + * 该套字库缺失 → 降级默认(不丢请求)+ WARN(meow_log→app 日志)。成功 0,越界负值。 */ +int meow_font_select_by_id(int id); + /* ============================================================ */ /* §10 字体分层 / 大字库(M2.5x:基座声明 + font 域已实现按档装载) */ /* ============================================================ */ diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index 082d937..e26f796 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -361,19 +361,22 @@ void meow_ui_swipe_hint(int x) * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); - /* x<0 视为清空(条长 0);条长上限=屏宽。 */ - if (x < 0) x = 0; - if (x > g_xres) x = g_xres; - /* 只按「新旧长度的差」增量绘制,避免整段重涂背景色: - * - 伸长:仅补画新增段(旧段已是 accent,不再用 bg 重涂——否则 app 底色≠theme bg - * 时会在旧段留下 bg 色块,就是残留残条); - * - 回缩/清空:仅把「超出新长度」的尾部填回背景色(实时回缩)。 */ - if (x > g_hint_last) { - meow_ui_fill_rect(g_hint_last, hy, x - g_hint_last, MUI_HINT_H, th->accent); - } else if (x < g_hint_last) { - meow_ui_fill_rect(x, hy, g_hint_last - x, MUI_HINT_H, th->bg); + /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长 + * 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */ + if (x > 0) { + if (x > g_xres) x = g_xres; + /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, + * 否则条只增不减(右滑变长、左滑回缩实时可见)。 */ + if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + /* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与 + * 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */ + meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg); + g_hint_last = x; + } else if (g_hint_last > 0) { + /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ + meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + g_hint_last = 0; } - g_hint_last = x; /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ } diff --git a/meow_ui_input.c b/meow_ui_input.c index c964f8c..1f27d2d 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -125,7 +125,6 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ -static int t_gmax_dx = 0; /* 本手势「最大右移位移」(t_cx-t_dx 的最大值,条长上限) */ /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -137,7 +136,6 @@ static void touch_press(int x, int y) t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; - t_gmax_dx = 0; /* 新手势:最大右移位移清零(条长从 0 计) */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ static void touch_update(int x, int y) @@ -147,17 +145,13 @@ static void touch_update(int x, int y) } if (x >= 0) t_cx = x; if (y >= 0) t_cy = y; - if (t_down && t_origin_valid) { - int dx = t_cx - t_dx; - if (dx > t_gmax_dx) t_gmax_dx = dx; /* 记录本手势已达的最大右移位移(条长上限) */ - } } static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; @@ -169,8 +163,7 @@ static void touch_release(void) * 被手势级清空,绝不继承上一手势坐标,确保: * ① 中部右滑不会误判成左缘返回(t_dx>=EDGE_W); * ② 先点中部再侧滑不返回(原点锁定在中部,>=EDGE_W),符合「必须从左缘起划」; - * ③ 返回判定要求右移严格正向(t_cx-t_dx>=0 且 ≥SWIPE_THRESH),杜绝「向左划也误返回」; - * 左划过程仅让进度条实时回缩,不额外生长(过程见上方 MEOW_EV_SWIPE_PROGRESS)。 + * ③ 右移严格正向(t_cx-t_dx>=0),杜绝「向左划也出条/误返回」。 * SDK 统一上报为 MEOW_SWIPE_LEFT(设计"左滑=返回"契约,hello 用 on_swipe(LEFT) 退出)。 * * 屏幕中部滑动不是返回手势:中部右滑→RIGHT(内容导航),中部左滑→非返回(落 TAP, @@ -199,7 +192,7 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; t_gmax_dx = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; } /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ @@ -231,22 +224,14 @@ static void *input_thread(void *arg) if (code == ABS_X || code == ABS_MT_POSITION_X) { if (t_down) touch_update(val, t_cy); /* 锁原点+更新当前(t_down 时才驱动) */ else t_cx = val; - /* 左滑进度实时上报(视觉条)。抬手触发仍由 touch_release 判定;这里只报过程。 - * 修复要点(左缘返回滑条 bug): - * ① 双向上报(不再要求 t_cx>t_dx):向右→条伸长、向左→条实时回缩, - * 摒弃「只在返回瞬间闪末帧 / 反向才出条」; - * ② 仅左缘起手(t_dx= 0 && t_dx < EDGE_W) { - int dx = t_cx - t_dx; - if (dx > t_gmax_dx) t_gmax_dx = dx; - int len = dx < 0 ? 0 : dx; - if (len > t_gmax_dx) len = t_gmax_dx; - push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, len); - } + /* 左滑进度实时上报(视觉条):原点确实在左缘(t_dxt_dx)——grow/回缩全程由「非全屏已验证的同一条路径」驱动,全屏仅 + * 垂直位置 hy 不同(hy=meow_ui_statusbar_height()),别无全屏特判。 + * 抬手触发仍由 touch_release 判定;这里只报过程(事件载荷 b=当前X)。 + * t_dx 是本次按下后首个有效 ABS 锁定的真实按下点(非残留),且每次抬手已 + * 清空,杜绝「中部点一下出条 / 向左划也出条」。 */ + if (t_down && t_origin_valid && t_dx >= 0 && t_dx < EDGE_W && t_cx > t_dx) + push_ev(MEOW_EV_SWIPE_PROGRESS, t_dx, t_cx); } else if (code == ABS_Y || code == ABS_MT_POSITION_Y) { if (t_down) touch_update(t_cx, val); else t_cy = val; @@ -335,17 +320,17 @@ void meow_ui_run(const meow_ui_events *ev) meow_ui_sys_notif_pull(); } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { if (in_startup) continue; /* 启动窗口进度条静音 */ - /* e.a=按下X(t_dx),e.b=条长 len(输入层已 clamp 到「本手势最大右移位移」且≥0)。 - * SDK 自动画顶部条:从 x=0 画到 len(右移位移),向右伸长、向左实时回缩。 - * M2.5x/A-13:全屏/收合下「左缘返回手势保留」,顶部动态提示条仍需绘制——不再以 - * meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则全屏时滑条完全不显示。 - * 条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿、全屏=0、收合=提示线下缘 - * (MUI_SBAR_COLLAPSE_H),与提示线不重叠加。APP 若注册 on_swipe_progress 仍可自绘 - * (系统条照常画);递交给 app 的是 (len-x_start) 使 x_start+dx==len,与系统条同长。 */ - int len = e.b; - meow_ui_swipe_hint(len); + int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ + /* SDK 自动画顶部条:长度=手指当前 X(=距左缘距离,向内划长度随位移增长)。 + * 全屏/收合下「左缘返回手势保留」,顶部动态提示条(meow_ui_swipe_hint)照常绘制 + * (与 agent-108:不再以 meow_ui_sys_statusbar_active()(全屏/隐藏=0)开关,否则 + * 全屏时滑条完全不显示)。条位 y=meow_ui_statusbar_height():非全屏=状态栏下沿 + * (28)、全屏=顶部、收合=提示线下缘(MUI_SBAR_COLLAPSE_H),与提示线不重叠加—— + * 全屏与非全屏共用同一条绘制/进度路径,只 hy 不同,无全屏特有的坐标/方向/最大值逻辑。 + * APP 若注册 on_swipe_progress 仍可自绘(系统条照常画;x_start+dx==当前X,幂等)。 */ + meow_ui_swipe_hint(xc); if (g_events_set && g_events.on_swipe_progress) - g_events.on_swipe_progress(e.a, len - e.a); + g_events.on_swipe_progress(x0, dx); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); } } From bebd28e92791cd42a9c1b9e7f338ad7632ff4b60 Mon Sep 17 00:00:00 2001 From: MeowOS Dev Date: Sun, 30 Aug 2026 14:30:25 +0800 Subject: [PATCH 08/49] =?UTF-8?q?meow=5Fsvcd=20status=20=E7=BE=8E=E5=8C=96?= =?UTF-8?q?(117)=20+=20=E6=8C=89=E9=92=AE=E6=8C=89=E4=BD=8F/=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E6=A0=B7=E5=BC=8F+=E6=BB=91=E6=9D=A1=E9=BB=91?= =?UTF-8?q?=E6=AE=8B=E5=BF=AB=E7=85=A7(118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui_gfx.c | 61 +++++++++++++++++++++++++++++++++++++++------- meow_ui_input.c | 33 +++++++++++++++++++++++-- meow_ui_internal.h | 12 +++++++++ meow_ui_widget.c | 41 +++++++++++++++++++++++++++++-- 4 files changed, 134 insertions(+), 13 deletions(-) diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index e26f796..a31e971 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -353,6 +353,49 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp #define MUI_HINT_H 5 static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */ +/* 顶部条区「快照恢复」:画条前把 [0..宽)×[hy..hy+MUI_HINT_H) 拍进小缓冲区;回缩/清空时 + * 回填快照还原「真实底色」而非 th->bg —— 全屏页面底=主题 accent,若填 th->bg(黑) 会在 + * accent 底上留下黑色残条(直到下次区域刷新才消失)。快照只在首现条时拍一次(后续 grow + * 不再覆盖快照),每行/全长布局全保留;缓冲为一次性分配(宽×H 行,480×5×2B≈4.8KB)。 */ +static unsigned char *g_hint_snap = NULL; /* 快照(g_line×MUI_HINT_H 字节;NULL=未拍/分配失败) */ +static int g_hint_snap_y = 0; /* 快照行的起始 y(本次手势时的 hy) */ + +/* 拍下「顶部条区」当前 back_buf 内容(只在 g_hint_last==0 的首现条时调用)。 */ +static void hint_snapshot(void) +{ + int hy = meow_ui_statusbar_height(); + if (hy < 0) hy = 0; + g_hint_snap_y = hy; + if (hy + MUI_HINT_H > g_yres) return; /* 越界:放弃(回缩走 bg 兜底) */ + size_t len = (size_t)MUI_HINT_H * (size_t)g_line; + if (!g_hint_snap) { + g_hint_snap = malloc(len); + if (!g_hint_snap) return; + } + memcpy(g_hint_snap, g_fb + (size_t)hy * g_line, len); +} + +/* 从快照回填列区间 [x0,x1) 的 MUI_HINT_H 行(还原条下真实底色)。x0/x1 已夹到屏宽。 */ +static void hint_restore(int x0, int x1) +{ + if (x1 <= x0) return; + if (x0 < 0) x0 = 0; + if (x1 > g_xres) x1 = g_xres; + if (!g_hint_snap) { /* 无快照(分配失败):兜底填背景(旧行为) */ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H, th->bg); + return; + } + int bpp = g_bpp / 8; + for (int r = 0; r < MUI_HINT_H; r++) { + size_t row = (size_t)(g_hint_snap_y + r) * g_line; + memcpy(g_fb + row + (size_t)x0 * bpp, + g_hint_snap + (size_t)r * g_line + (size_t)x0 * bpp, + (size_t)(x1 - x0) * bpp); + } + meow_gfx_mark_dirty(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H); /* 标脏:调用方 flush_region 上屏 */ +} + void meow_ui_swipe_hint(int x) { if (!g_init_ok) return; @@ -361,20 +404,19 @@ void meow_ui_swipe_hint(int x) * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); - /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长 - * 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */ + /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先把 + * 超出部分还原为快照、x<=0 整条还原。x<0 视为清空;条长上限=屏宽。条色仍取 th->fg。 */ if (x > 0) { if (x > g_xres) x = g_xres; - /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, - * 否则条只增不减(右滑变长、左滑回缩实时可见)。 */ - if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); - /* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与 - * 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */ + if (g_hint_last == 0) hint_snapshot(); /* 首现条:拍下未污染区,供此后回填 */ + /* 往回滑(新长度 < 上次)时,把「上次画到旧长度、本次超出的 [x,old)」还原为快照 + * 真实底色(全屏 accent),而非填 th->bg —— 否则左滑回缩在 accent 底上漏黑。 */ + if (g_hint_last > x) hint_restore(x, g_hint_last); meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg); g_hint_last = x; } else if (g_hint_last > 0) { - /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ - meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + /* 手指回原点/抬手未达标/抬手达标:整条还原为快照真实底色(全屏 accent 上无黑残) */ + hint_restore(0, g_hint_last); g_hint_last = 0; } /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ @@ -832,6 +874,7 @@ void meow_ui_shutdown(void) if (g_fb_fd >= 0) close(g_fb_fd); if (g_back_buf) free(g_back_buf); if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; } + if (g_hint_snap) { free(g_hint_snap); g_hint_snap = NULL; } g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL; g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0; dirty_reset(); diff --git a/meow_ui_input.c b/meow_ui_input.c index 1f27d2d..aa19944 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -125,6 +125,7 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ +static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -136,6 +137,7 @@ static void touch_press(int x, int y) t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; + t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ static void touch_update(int x, int y) @@ -151,7 +153,7 @@ static void touch_release(void) if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; @@ -192,7 +194,34 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; +} + +/* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)---- + * 直接读触摸状态机当前值(与语义事件队列无关;无按下/坐标未就绪时 x/y 返回 -1)。 */ +int meow_input_touch_down(void) { return t_down; } +int meow_input_touch_x(void) { return t_cx; } +int meow_input_touch_y(void) { return t_cy; } +unsigned int meow_input_touch_hold_ms(void) +{ + if (!t_down) return 0; + uint64_t e = now_ms() - t_down_ms; + if (e > 0xFFFFFFFFu) e = 0xFFFFFFFFu; + return (unsigned int)e; +} + +/* 宿主单测/内部注入:模拟按压(down=1 时 x,y=压点、hold_ms=已按住时长),设备运行时无调用方。 */ +void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) +{ + if (down) { + t_down = 1; t_press_valid = 1; t_origin_valid = 1; + t_cx = x; t_cy = y; t_dx = x; t_dy = y; + uint64_t now = now_ms(); + t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0; + } else { + t_down = 0; t_press_valid = 0; t_origin_valid = 0; + t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; + } } /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 849c6d2..7767568 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -52,6 +52,18 @@ void meow_input_inject(int type, int a, int b); /* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ void meow_input_drain(void); +/* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)---- + * 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw + * 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */ +int meow_input_touch_down(void); /* 当前是否有触摸按下 */ +int meow_input_touch_x(void); /* 当前 X(无按下=-1) */ +int meow_input_touch_y(void); /* 当前 Y(无按下=-1) */ +unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒(抬起后 0) */ + +/* 宿主单测/内部注入:模拟一次按压。down=1 时 x,y 为压点、hold_ms 可指定「已按住时长」 + * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ +void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index e7d0cac..58c2f4d 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -11,6 +11,20 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; +/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。 + * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ +#define MEOW_BTN_HOLD_MS 500u + +/* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 + * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ +static uint32_t blend_color(uint32_t c, uint32_t o, int t) +{ + int rr = (((c >> 16) & 0xFF) * (100 - t) + ((o >> 16) & 0xFF) * t) / 100; + int gg = (((c >> 8) & 0xFF) * (100 - t) + ((o >> 8) & 0xFF) * t) / 100; + int bb = ((c & 0xFF) * (100 - t) + (o & 0xFF) * t) / 100; + return ((uint32_t)rr << 16) | ((uint32_t)gg << 8) | (uint32_t)bb; +} + struct meow_widget { int type; int x, y, w, h; @@ -89,8 +103,31 @@ void meow_widget_draw(meow_widget *w) const meow_theme *th = meow_theme_get(); switch (w->type) { case W_BTN: { - uint32_t bg = w->pressed ? th->dim : th->accent; - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); + /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路: + * - w->pressed:应用在 on_tap 命中后标记(既有语义,保留); + * - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。 + * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。 */ + int live = meow_input_touch_down() && + meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && + meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; + int press = w->pressed || live; + int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + if (hold) { + /* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调; + * 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧, + * 非本「加视觉态」范围),不做 1-2 帧插值。 */ + uint32_t hbg = blend_color(th->accent, 0x000000, 24); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg); + if (w->w > 6 && w->h > 6) + meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9, + blend_color(th->accent, 0xFFFFFF, 45)); + } else if (press) { + /* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */ + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, + blend_color(th->accent, 0x000000, 12)); + } else { + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent); + } meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, 0xFFFFFF); break; From 143c11ac94bd7ba034cc8952ad990d81317b5fc3 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 15:27:10 +0800 Subject: [PATCH 09/49] =?UTF-8?q?M2.60=E2=86=92M2.61=20=E9=AA=A8=E6=9E=B6:?= =?UTF-8?q?=20SDK=20Activity=20=E6=A0=88=20+=20=E6=8E=A7=E4=BB=B6=E5=BD=92?= =?UTF-8?q?=E5=B1=9E(agent-122)=20+=20=E6=8C=89=E9=92=AE=E5=8E=BB=E5=9C=88?= =?UTF-8?q?/=E5=8D=B3=E6=97=B6=E9=87=8D=E7=BB=98(agent-119)=20+=20SDK=20?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=96=87=E6=A1=A3(agent-121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SDK §6.1 三层控件模型: Activity栈(push/pop/active) + 页面控件归属 owner + meow_ui_global_* 骨架(②) + 系统级仅内部接口(③) - meow_widget 加 owner; live_btn_hit/meow_widget_hit 仅 active activity 命中; touch_refresh stale 保护 - 根治"全屏页触摸主页按钮闪现"(跨页面控件残留); 控件对象复用弹栈不重建 - hello 迁移双 Activity(home/full): 全屏左滑=弹栈回主页, 根左滑=退出 - agent-119 按钮去圈(纯色accent+黑12/24%)+即时局部重绘保留; agent-121 SDK模板§5.3-5.7 --- build_sdk.sh | 2 +- meow_ui.h | 37 ++++++++++++ meow_ui_activity.c | 33 +++++++++++ meow_ui_gfx.c | 4 ++ meow_ui_global.c | 61 ++++++++++++++++++++ meow_ui_input.c | 7 +++ meow_ui_internal.h | 13 +++++ meow_ui_widget.c | 140 +++++++++++++++++++++++++++++++++++++++------ 8 files changed, 277 insertions(+), 20 deletions(-) create mode 100644 meow_ui_activity.c create mode 100644 meow_ui_global.c diff --git a/build_sdk.sh b/build_sdk.sh index 6589f06..b0fe874 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index e7372c3..04120d9 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -156,6 +156,43 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/ * x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */ void meow_ui_swipe_hint(int x); +/* ============================================================ */ +/* §6.1 控件生命周期与归属(Activity 栈 + 三层控件模型) [源:2026-08-30] */ +/* ============================================================ */ +/* 根治「跨页面控件残留」:控件命中/绘制必须严格绑定其所属页面,否则会出现 + * "按钮已不可见但仍被触摸命中"(如全屏页触摸到主页按钮位置闪现 accent 底)。 + * + * 三层职责(从 App 到系统,风险从低到高): + * ① 页面控件:归属唯一 owner Activity;命中/实时按压只在「当前 active Activity + * 的控件集」里生效。对象不销毁(同 Activity 内复用),弹栈回来自动恢复 → + * 回页面零重建。 + * ② App 跨 Activity 控件(SDK 托管):钉在 App 的 Activity 栈之上、跨 Activity 常驻, + * 逻辑/渲染由 SDK 调度。本轮为「可用骨架」(见上 API 注释),待 B2 细化。 + * ③ 系统级控件(状态栏/通知中心/返回滑条/电源菜单):仅框架内部,无公共 API(不开放)。 + * + * 触摸命中分流顺序:③ 系统全局 → ② App 全局 → ① 当前 active Activity 页面控件; + * 某层命中即消费,否则落到下一层。 */ + +/* ① Activity 栈。控件创建时绑定「当前 active Activity」为 owner;弹栈回来自动恢复。 + * 栈空时 active() 返回 0(根/default Activity),故未显式管理栈的 App 默认所有控件 + * 归属根 Activity 0。push 新 Activity(进子页/全屏)、pop 回上级(回根)。 */ +void meow_ui_activity_push(int id); +void meow_ui_activity_pop(void); +int meow_ui_activity_active(void); /* 当前 active activity id(栈空=0) */ + +/* ② App 跨 Activity 控件(SDK 托管,骨架)。SDK 维护全局控件列表: + * - 渲染:每帧(有脏区需上屏时,meow_ui_gfx present())在 active activity 内容之上、 + * 全局状态栏之下调用 render_cb(跨 Activity 常驻); + * - 触摸:TAP 先给 global hit_cb(返回 1=消费),否则落到 active activity 页面控件。 + * 逻辑/渲染由 SDK 调度,App 只需声明。注:骨架,待 B2 细化。 */ +typedef void (*meow_global_render_fn)(void *ctx); /* 每次渲染帧回调(SDK 调用) */ +typedef int (*meow_global_hit_fn)(void *ctx, int x, int y); /* 命中返回1=消费 */ +void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx); +void meow_ui_global_remove(int id); + +/* ③ 系统级控件:状态栏/通知中心/左缘返回滑条/电源菜单等全局叠加层——仅 SDK 内部接口, + * 不向用户 App 开放任何公共悬浮/叠加层 API。既有状态栏/返回滑条逻辑(§3.1)不动。 */ + /* ============================================================ */ /* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */ /* ============================================================ */ diff --git a/meow_ui_activity.c b/meow_ui_activity.c new file mode 100644 index 0000000..205d215 --- /dev/null +++ b/meow_ui_activity.c @@ -0,0 +1,33 @@ +/* + * meow_ui_activity.c — Activity 栈(§6.1 ①)。 + * + * 控件严格归属创建它的 Activity;命中/实时按压只在「当前 active Activity」的控件集里 + * 生效(见 meow_ui_widget.c 的 owner 绑定 + live_btn_hit/meow_widget_hit 过滤)。 + * 栈空时 active() 返回 0(根/default Activity)。 + * + * 依赖:无(纯静态数组 + 无外部符号),宿主/真机均可链入,便于单测。 + * 语义:push = 压栈(新 Activity 成为 active); pop = 弹栈(回上级,栈空则停在根 0)。 + */ +#include "meow_ui.h" + +#define MEOW_ACT_MAX 32 + +static int g_act[MEOW_ACT_MAX]; +static int g_act_n = 0; + +void meow_ui_activity_push(int id) +{ + if (g_act_n >= MEOW_ACT_MAX) + return; /* 栈满:静默钳制(骨架级,不崩) */ + g_act[g_act_n++] = id; +} + +void meow_ui_activity_pop(void) +{ + if (g_act_n > 0) g_act_n--; +} + +int meow_ui_activity_active(void) +{ + return g_act_n > 0 ? g_act[g_act_n - 1] : 0; +} diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index a31e971..a056516 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -652,6 +652,10 @@ static void present(void) if (!g_refresh_always_max) { if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return; } + /* §6.1 ②:App 跨 Activity 全局控件(SDK 托管骨架)。在 active activity 内容之上、 + * 全局状态栏之下绘制——跨 Activity 常驻,仅在有帧需上屏(已过帧率门控)时调用。 + * render_cb 内的 meow_ui_* 绘制会自行标脏,随下方 dirty 列表一并上屏。 */ + meow_ui_global_render(); /* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。 * 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行), * app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0 diff --git a/meow_ui_global.c b/meow_ui_global.c new file mode 100644 index 0000000..d91dc20 --- /dev/null +++ b/meow_ui_global.c @@ -0,0 +1,61 @@ +/* + * meow_ui_global.c — App 跨 Activity 控件(§6.1 ②,SDK 托管骨架)。 + * + * SDK 维护一个全局控件列表(钉在 App 的 Activity 栈之上、跨 Activity 常驻): + * - 渲染:meow_ui_gfx present() 每帧在 active activity 内容之上、全局状态栏之下调 + * meow_ui_global_render()(遍历列表调 render_cb); + * - 触摸:meow_ui_input TAP 分发先给 meow_ui_global_hit(x,y)——命中返回 1=消费, + * 否则落到 active activity 页面控件(①)。 + * + * 骨架(待 B2 细化):无 z 序细分/无按压态管理/无命中区排布——App 的 render/hit 回调 + * 自行决定绘制与命中。跨 Activity 常驻由「列表不随 Activity 栈变化」天然保证。 + */ +#include "meow_ui.h" + +#define MEOW_GLOBAL_MAX 16 + +typedef struct { + int id; + meow_global_render_fn render; + meow_global_hit_fn hit; + void *ctx; +} gentry; + +static gentry g_globals[MEOW_GLOBAL_MAX]; +static int g_global_n = 0; + +void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].id == id) return; /* 同 id 幂等(重复 add 不新增,骨架) */ + if (g_global_n >= MEOW_GLOBAL_MAX) return; + g_globals[g_global_n].id = id; + g_globals[g_global_n].render = r; + g_globals[g_global_n].hit = h; + g_globals[g_global_n].ctx = ctx; + g_global_n++; +} + +void meow_ui_global_remove(int id) +{ + for (int i = 0; i < g_global_n; i++) { + if (g_globals[i].id == id) { + g_globals[i] = g_globals[--g_global_n]; + return; + } + } +} + +void meow_ui_global_render(void) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].render) g_globals[i].render(g_globals[i].ctx); +} + +int meow_ui_global_hit(int x, int y) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].hit && g_globals[i].hit(g_globals[i].ctx, x, y)) + return 1; + return 0; +} diff --git a/meow_ui_input.c b/meow_ui_input.c index aa19944..c234ca7 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -310,6 +310,10 @@ void meow_ui_run(const meow_ui_events *ev) startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */ while (!g_exit) { + /* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。 + * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */ + if (!meow_ime_active()) meow_widget_touch_refresh(); + meow_input_ev e; int got = 0; while (meow_input_pop(&e)) { @@ -323,6 +327,9 @@ void meow_ui_run(const meow_ui_events *ev) meow_ui_swipe_hint(-1); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue; + /* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件 + * (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */ + if (meow_ui_global_hit(e.a, e.b)) continue; if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b); } else if (e.type == MEOW_EV_KEY) { if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b); diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 7767568..ff151e2 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -64,6 +64,19 @@ unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒 * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); +/* ---- 控件层实时刷新(meow_ui_widget.c)---- + * 每帧由 meow_ui_run 调用(主线程):实时触摸按下/释放/按住达阈值时,重绘命中的按钮区 + * 并局部上屏,让按压/按住/释放视觉即时反映,无需 app 在 on_tick/on_tap 重绘(agent-119)。 + * 仅作用于 SDK 控件按钮(meow_widget_draw 的 W_BTN);app 纯自绘不适用。 */ +void meow_widget_touch_refresh(void); + +/* ---- App 跨 Activity 全局控件(meow_ui_global.c,§6.1 ②骨架)---- + * 渲染帧(meow_ui_gfx present())在 active activity 内容之上、全局状态栏之下调用 + * meow_ui_global_render;触摸 TAP 分发先给 meow_ui_global_hit(命中返回 1=消费, + * 否则落到 active activity 页面控件)。跨 Activity 常驻,由 SDK 调度。 */ +void meow_ui_global_render(void); +int meow_ui_global_hit(int x, int y); + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index 58c2f4d..00c8f77 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -11,7 +11,7 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; -/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。 +/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u @@ -28,6 +28,7 @@ static uint32_t blend_color(uint32_t c, uint32_t o, int t) struct meow_widget { int type; int x, y, w, h; + int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */ char label[128]; int pressed; /* 按钮按下视觉态 */ int min, max, val; /* 滑条 */ @@ -37,12 +38,105 @@ struct meow_widget { void *ctx; }; +/* ---- 实时按压即时刷新(agent-119)---- + * 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在 + * on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/ + * 按住达阈值时主动重绘命中的按钮区域并局部上屏(不等 app 重绘),即时反映按下变深/ + * 释放恢复,连续快速点击每按都反映。仅针对 SDK 控件按钮(W_BTN);app 纯自绘不适用。 + * 语义不变:on_tap / meow_btn_pressed / meow_widget_hit 零改动;非按钮区域不动。 */ +#define MEOW_LIVE_BTN_MAX 64 +static meow_widget *g_live_btns[MEOW_LIVE_BTN_MAX]; +static int g_live_btn_n = 0; +static meow_widget *g_live_btn = NULL; /* 实时按压中最上层按钮(无 = NULL) */ +static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住态 */ + +static void live_btn_register(meow_widget *w) +{ + if (!w || w->type != W_BTN) return; + for (int i = 0; i < g_live_btn_n; i++) + if (g_live_btns[i] == w) return; + if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w; +} + +static void live_btn_unregister(meow_widget *w) +{ + for (int i = 0; i < g_live_btn_n; i++) { + if (g_live_btns[i] == w) { + g_live_btns[i] = g_live_btns[--g_live_btn_n]; + if (g_live_btn == w) g_live_btn = NULL; + return; + } + } + if (g_live_btn == w) g_live_btn = NULL; +} + +/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。 + * §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件 + * 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */ +static meow_widget *live_btn_hit(int x, int y) +{ + int active = meow_ui_activity_active(); + meow_widget *r = NULL; + for (int i = 0; i < g_live_btn_n; i++) { + meow_widget *w = g_live_btns[i]; + if (w->owner != active) continue; /* 非 active activity:跳过 */ + if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h) + r = w; /* 越后创建越上:保留最后命中 */ + } + return r; +} + +/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */ +static void live_btn_redraw(meow_widget *w) +{ + if (!w) return; + meow_widget_draw(w); + meow_ui_flush_region(w->x, w->y, w->w, w->h); +} + +/* 由 meow_ui_run 每帧调用(主线程):检测实时触摸状态迁移,即时重绘命中按钮的按压/按住/ + * 释放,不等 app on_tick/on_tap。仅在触摸状态相对上次渲染发生变化时动作(非全屏、省性能)。 */ +void meow_widget_touch_refresh(void) +{ + int active = meow_ui_activity_active(); + /* §6.1 ①:若当前按住按钮已不属于 active activity(按压期间发生了 Activity 切换, + * 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上 + * 画出旧按钮的 accent 残留)。 */ + if (g_live_btn && g_live_btn->owner != active) { + g_live_btn = NULL; + g_live_prev_hold = 0; + } + int down = meow_input_touch_down(); + meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL; + int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + + if (!down) { + /* 释放:把最近按压按钮恢复为常态 */ + if (g_live_btn) { live_btn_redraw(g_live_btn); g_live_btn = NULL; } + g_live_prev_hold = 0; + return; + } + if (hit != g_live_btn) { + /* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */ + if (g_live_btn) live_btn_redraw(g_live_btn); + g_live_btn = hit; + g_live_prev_hold = hold; + if (hit) live_btn_redraw(hit); + } else if (hit && hold != g_live_prev_hold) { + /* 同一按钮按住达阈值:升级到更强态 */ + live_btn_redraw(hit); + g_live_prev_hold = hold; + } +} + meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */ if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + live_btn_register(t); return t; } @@ -51,6 +145,7 @@ meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val) meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40; + t->owner = meow_ui_activity_active(); t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; return t; } @@ -60,6 +155,7 @@ meow_widget *meow_switch_create(int x, int y, int on) meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32; + t->owner = meow_ui_activity_active(); t->on = on ? 1 : 0; return t; } @@ -70,11 +166,17 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count, meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; return t; } -void meow_widget_free(meow_widget *w) { if (w) free(w); } +void meow_widget_free(meow_widget *w) +{ + if (!w) return; + live_btn_unregister(w); + free(w); +} static int hit(meow_widget *w, int x, int y) { @@ -82,7 +184,13 @@ static int hit(meow_widget *w, int x, int y) return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h; } -int meow_widget_hit(meow_widget *w, int x, int y) { return hit(w, x, y); } +int meow_widget_hit(meow_widget *w, int x, int y) +{ + if (!w) return 0; + /* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */ + if (w->owner != meow_ui_activity_active()) return 0; + return hit(w, x, y); +} int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; } void meow_slider_set(meow_widget *w, int val) @@ -112,22 +220,16 @@ void meow_widget_draw(meow_widget *w) meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; int press = w->pressed || live; int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; - if (hold) { - /* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调; - * 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧, - * 非本「加视觉态」范围),不做 1-2 帧插值。 */ - uint32_t hbg = blend_color(th->accent, 0x000000, 24); - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg); - if (w->w > 6 && w->h > 6) - meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9, - blend_color(th->accent, 0xFFFFFF, 45)); - } else if (press) { - /* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */ - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, - blend_color(th->accent, 0x000000, 12)); - } else { - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent); - } + /* 纯色状态层(无描边环、无分层):一层即满,深浅二态均由 accent 向黑调出。 + * 常态=accent、按下=accent+黑12%、按住=accent+黑24%。整体一层不「先底色再套圈」。 */ + uint32_t bg; + if (hold) + bg = blend_color(th->accent, 0x000000, 24); + else if (press) + bg = blend_color(th->accent, 0x000000, 12); + else + bg = th->accent; + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, 0xFFFFFF); break; From 9f4143af22eab24b6a9926483fd3512f12c5ec50 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 18:42:59 +0800 Subject: [PATCH 10/49] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=99=A8SDK=E6=94=B9?= =?UTF-8?q?=E9=80=A0=20=E9=98=B6=E6=AE=B5=E4=B8=80:=20=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E6=94=B6=E6=95=9B=E5=8D=95=E4=B8=80SDK=E5=85=A8?= =?UTF-8?q?=E5=B1=80(agent-136)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - launcher meow_ui_set_statusbar(1) 启用SDK全局状态栏; 旧自绘(draw_statusbar/sb_redraw等)#if0退役(保留回退) - 新增 SDK meow_ui_sys_feed(): launcher 喂 batt/charging/wifi/bt/vol/brightness 进SDK快照+置force+标脏顶条(0,0,xres,28)→present只重画顶条 - meow_ui_theme_reload(): SDK状态栏跟手launcher换肤; meow_sys_state 增 brightness - 主界面/设置页/电源菜单/通知中心 顶部28px 由SDK全局状态栏统一绘制; 与App同实现/同主题/同日·夜 - ⚠️ 状态栏由MD3图标式→SDK文本式(HH:MM:SS …81% WIFI 蓝牙 VOL 亮度) — 用户拍板"用SDK的"既定结果; 图标MD3化属后续 - meow_ui.a=110c820a(仅新增符号,向后兼容) launcher=d4f108dd hello=12f48840(重编确认App不破坏); 0warning - devdocs/plan/LAUNCHER_SDK_REWRITE.md 方案先行 --- meow_ui_internal.h | 27 +++++++++++++++++++++++++++ meow_ui_sys.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- meow_ui_sys.h | 2 ++ meow_ui_theme.c | 9 +++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/meow_ui_internal.h b/meow_ui_internal.h index ff151e2..702a97b 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -8,6 +8,33 @@ #include #include "meow_ui.h" +/* ---- 系统状态喂入(Stage-One 状态栏收敛;meow_ui_sys.c 提供)---- + * launcher(系统进程)不跑 SDK 事件循环(无 meow_ui_run),SDK 的 meow_ui_sys_refresh() + * 不会在 launcher 里自动触发;且电量/音量/亮度等权威值本就在 launcher 的 g_sys 快照里 + * (launcher 是背光/音量写入方)。故此 API 让 launcher 把权威快照喂进 SDK 的系统状态, + * 喂入即置状态栏 force,下一次 present() 重画顶条。SDK 自身 meow_ui_sys_refresh() 仍按 + * 同源兜底(供 App 流程 meow_ui_run 使用)。只填 SDK 快照所需最小字段,与 launcher 的 + * meow_sys_state(meow_launcherd.c)字段名交叉映射,二者是不同 TU 的异名类型,故用此 + * 专用喂入结构,避免 launcher 引入 meow_ui_sys.h 造成 typedef 重定义。 */ +typedef struct meow_ui_feed { + int batt; /* 电量 %,-1 未知 */ + int charging; /* 1 充电/满 0 放电 */ + int wifi_on; /* 1 网卡 up(operstate==up) */ + int bt_seen; /* hci0 节点存在 */ + int bt_on; /* 实际上电 */ + int vol; /* 音量 0-100 */ + int brightness; /* 背光 1-100;<=0 视为未知/屏关不入顶条 */ +} meow_ui_feed; +void meow_ui_sys_feed(const meow_ui_feed *f); /* 喂权威快照 + 置 force 重绘顶条 */ + +/* ---- 主题(meow_ui_theme.c 提供)---- + * 重置主题缓存,使下一次 meow_theme_get() 重读 settings.conf(display.theme / + * display.theme_mode / display.theme_dark_start|end)。launcher 在 statusbar 刷新前调用, + * 让 SDK 状态栏(其主题是缓存的)跟手 launcher 侧主题变更,避免「launcher 界面已换肤、 + * 状态栏滞留旧主题」。App 无需调用(主题变更本就由 Settings 改 settings.conf,下一个 + * set_mode/get 自然读到——缓存仅挡「同会话内 settings 被外部改写」的即时反映)。 */ +void meow_theme_reload(void); + /* ---- 渲染后端(meow_ui_gfx.c)提供 ---- */ /* 把 RGBA 像素(stb_image 输出,4 通道 straight alpha)blit 进 back_buf; * alpha 为全局不透明度 0-255(255 时仍用像素自身 alpha 做逐像素混合)。 */ diff --git a/meow_ui_sys.c b/meow_ui_sys.c index e21900e..99d7d3b 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -91,11 +91,26 @@ static int settings_vol(void) return meow_ui_vol_clamp(vol, vol_max); } +/* 亮度:读 settings.conf 的 display.brightness(launcher 同源),并 clamp 到 + * [display.brightness_min, 100](与 launcher clamp_brightness 同口径,下限默认 1=极暗非全黑)。 + * Stage-One 状态栏收敛(launcher 退役自绘状态栏)后,launcher 侧用 meow_ui_sys_feed() 喂 + * 即时值(写入方=launcher);此处兜底让 App(meow_ui_run 的 1s 轻刷)也读到并显示亮度, + * 保证「launcher / App 同一实现、同日/夜」。 */ +static int settings_bright(void) +{ + int v = meow_ui_vol_read(MEOW_SETTINGS_FILE, "display.brightness", 101); + int mn = meow_ui_vol_read(MEOW_SETTINGS_FILE, "display.brightness_min", 1); + if (v < mn) v = mn; + if (v > 100) v = 100; + return v; +} + int meow_ui_sys_init(void) { memset(&g_sys, 0, sizeof g_sys); g_sys.batt = -1; g_sys.vol = settings_vol(); + g_sys.brightness = settings_bright(); g_statusbar_on = 1; g_fullscreen = 0; g_statusbar_force = 1; @@ -117,6 +132,7 @@ void meow_ui_sys_refresh(void) g_sys.wifi_on = !strcmp(op, "up") ? 1 : 0; g_sys.vol = settings_vol(); + g_sys.brightness = settings_bright(); /* 兜底读 settings;launcher 喂入会覆盖(更即时) */ /* 蓝牙:hci0 存在性检查便宜;上电状态贵(hciconfig spawn),30s 缓存。 */ static uint64_t last_bt_ms = 0; @@ -136,6 +152,25 @@ void meow_ui_sys_refresh(void) const meow_sys_state *meow_ui_sys_get(void) { return &g_sys; } +/* ---- 系统进程(launcher)喂入权威快照(Stage-One 状态栏收敛)---- + * launcher 不跑 SDK 事件循环,其 g_sys(meow_sys_refresh/light + 事件路径维护)是权威数据源 + * (尤其亮度/音量,写入方=launcher)。此处把它覆盖进 SDK 快照并置 force,驱动下一次 + * present() 重画顶条。不喂的字段保持 SDK 现有值(由 meow_ui_sys_refresh 同源兜底)。 */ +void meow_ui_sys_feed(const meow_ui_feed *f) +{ + if (!f) return; + g_sys.batt = f->batt; + g_sys.charging = f->charging; + g_sys.wifi_on = f->wifi_on; + g_sys.bt_seen = f->bt_seen; + g_sys.bt_on = f->bt_on; + g_sys.vol = f->vol; + if (f->brightness <= 0) g_sys.brightness = 0; /* 未知/屏关:不入顶条(>0 才显示) */ + else if (f->brightness > 100) g_sys.brightness = 100; + else g_sys.brightness = f->brightness; + g_statusbar_force = 1; /* 数据已变 → 顶条必重画 */ +} + /* ---- 公共 API ---- */ void meow_ui_set_statusbar(int visible) { @@ -190,20 +225,25 @@ int meow_ui_sys_statusbar_force(void) return f; } -/* 状态栏右侧信息串:batt% WIFI 蓝牙/蓝牙 VOL nn%(同 launcher statusbar_right_str) */ +/* 状态栏右侧信息串:batt% WIFI 蓝牙 VOL nn% 亮度nn%(同 launcher 状态栏右侧语义,文本式)。 + * mfont 就绪用 CJK「蓝牙/亮度」,否则回退 ASCII "BT/BRI"(28px 栏紧凑,避免超宽叠到时钟)。 + * 顺序与 launcher 右侧簇一致:电量 → WiFi → 蓝牙 → 音量 → 亮度。*/ static void statusbar_right_str(char *out, size_t n) { - char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = ""; + char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = "", bri[20] = ""; if (g_sys.batt >= 0) snprintf(batt, sizeof batt, "%d%%", g_sys.batt); if (g_sys.wifi_on) snprintf(wifi, sizeof wifi, "WIFI"); snprintf(vol, sizeof vol, "VOL %d%%", g_sys.vol); if (g_sys.bt_seen && g_sys.bt_on) snprintf(bt, sizeof bt, meow_gfx_mfont_ready() ? "蓝牙" : "BT"); + if (g_sys.brightness > 0) /* >0 才显示(屏常亮态);0=屏关/未知不入 */ + snprintf(bri, sizeof bri, meow_gfx_mfont_ready() ? "亮度%d%%" : "BRI %d%%", g_sys.brightness); out[0] = 0; if (batt[0]) strcat(out, batt); if (wifi[0]) { if (out[0]) strcat(out, " "); strcat(out, wifi); } if (bt[0]) { if (out[0]) strcat(out, " "); strcat(out, bt); } if (vol[0]) { if (out[0]) strcat(out, " "); strcat(out, vol); } + if (bri[0]) { if (out[0]) strcat(out, " "); strcat(out, bri); } } void meow_ui_sys_statusbar_render(void) diff --git a/meow_ui_sys.h b/meow_ui_sys.h index de80a7d..070e61b 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -27,6 +27,8 @@ typedef struct meow_sys_state { int bt_seen; /* hci0 节点存在 */ int bt_on; /* 实际上电(hciconfig UP) */ int vol; /* 音量 0-100 */ + int brightness; /* 背光/亮度 1-100;只有 >0 才显示(屏常亮态)。由 launcher 喂入 + * (launcher 是背光写入/clamp 方),SDK 自己亦用 settings 兜底读。 */ } meow_sys_state; /* 顶部状态栏几何(与 launcher SB_BAR_H=28 / SB_FONT=20 对齐,视觉统一)。 diff --git a/meow_ui_theme.c b/meow_ui_theme.c index 305ad9a..c567ee8 100644 --- a/meow_ui_theme.c +++ b/meow_ui_theme.c @@ -11,6 +11,7 @@ * 切换即广播 listener。AUTO 解析只在查询时按当前系统时间计算(不缓存)。 */ #include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_theme_reload() 定义(供 launcher 逼重读 settings) */ #include #include #include @@ -168,6 +169,14 @@ void meow_theme_set_mode(meow_theme_mode m) broadcast(); } +/* 重置缓存:下一次 meow_theme_get()/get_mode()/defaults() 重读 settings.conf。 + * launcher 在 statusbar 刷新前调用,让 SDK 全局状态栏(其主题缓存于 g_loaded)跟手 + * launcher 侧主题变更(settings.conf 被改后立即反映),避免「界面已换肤、状态栏滞留旧主题」。 */ +void meow_theme_reload(void) +{ + g_loaded = 0; +} + meow_theme meow_theme_defaults(meow_theme_mode m) { switch (resolve_mode(m)) { From c0f78750d4bf6da942429656aeccc036718f4da7 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 19:32:40 +0800 Subject: [PATCH 11/49] =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=A0=8F=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E9=97=AD=E7=8E=AF(agent-142):=20=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=BB=9F=E4=B8=80=E6=B8=B2=E6=9F=93+App?= =?UTF-8?q?=E4=B8=8D=E7=94=BB+=E8=A6=86=E7=9B=96=E5=8F=A0=E5=8A=A0+?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=94=AF=E4=B8=80+MD3=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E5=BC=8F+=E4=B8=BB=E9=A2=98=E6=8D=A2=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - meow_ui_sys_set_system_mode: launcher置1才渲染, App默认0→不画状态栏; app_overlay_statusbar在launch_app循环~1s叠加28px顶条(不SIGSTOP App) - 数据源唯一(launcher g_sys→meow_ui_sys_feed; wifi_level/batt/vol/bri/clock), 根治"launcher有亮度App没有" - 右侧=MD3图标+变量值(statusbar_right_str文本退役→图标簇, 复用sb_icons.h/MEOW_SB_ICON_*/meow_gfx_blit_icon); 颜色随meow_theme - ui.style换图标集(meow_ui_style/set_style读ui.style md3/miuix, MIUIx暂MD3占位) - hello最小改动(redraw非全屏只清inset=28之下, 不冲系统顶条); 开放点: App不SIGSTOP/inset28/MIUIx占位/set_statusbar系统模式门 - meow_ui.a=3da5af7b launcher=a2b1c096 hello=67075741 0warning; 宿主test_sb_statusbar ALL PASS + render PPM目检(左时钟右亮度/WiFi/蓝牙/音量/电量) - 遗留: 真机验证(无双画/数据一致/顶条叠加/inset/ui.style); 运行时fullscreen沉浸叠画待M3; MIUIx图标占位 --- meow_ui.h | 11 +++ meow_ui_gfx.c | 63 +++++++++++- meow_ui_input.c | 7 +- meow_ui_internal.h | 53 ++++++++++- meow_ui_sys.c | 122 ++++++++++++++++++------ meow_ui_sys.h | 4 +- meow_ui_theme.c | 32 +++++++ render_sb_statusbar.c | 51 ++++++++++ sb_icons.h | 216 ++++++++++++++++++++++++++++++++++++++++++ test_sb_statusbar.c | 113 ++++++++++++++++++++++ 10 files changed, 636 insertions(+), 36 deletions(-) create mode 100644 render_sb_statusbar.c create mode 100644 sb_icons.h create mode 100644 test_sb_statusbar.c diff --git a/meow_ui.h b/meow_ui.h index 04120d9..bcb94b7 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -276,6 +276,17 @@ void meow_theme_remove_listener(meow_theme_listener cb); /* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */ meow_theme meow_theme_defaults(meow_theme_mode m); +/* ---- §8.1 顶级主题风格族(minimal,2026-08-30)---- + * 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集 + * 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/ + * 广播(让状态栏/控件下一帧用新 style 重绘)。style 还决定状态栏右侧图标集(§3.1 同一个状态栏)。 + * 完整 style×mode 配色/控件形态矩阵(§8.1.3)为后续里程碑。 */ +#define MEOW_STYLE_MD3 0 /* Material You(默认) */ +#define MEOW_STYLE_MIUIX 1 /* MIUI 风格族(基础占位) */ + +void meow_ui_set_style(int style); /* 设顶级主题风格族;越界 clamp 到合法值;同值忽略 */ +int meow_ui_style(void); /* 当前 style(默认 MEOW_STYLE_MD3) */ + /* ============================================================ */ /* §9 应用日志(统一日志 API,M2.51 最小可用版) */ /* ============================================================ */ diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index a056516..6c7d3a5 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -11,6 +11,7 @@ #include "meow_ui.h" #include "meow_ui_internal.h" #include "meow_ui_sys.h" +#include "sb_icons.h" #include "../meow_launcherd/meow_font.h" #include @@ -181,6 +182,66 @@ void meow_gfx_mark_full(void) { dirty_full(); } * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ unsigned char *meow_ui_back_buf(void) { return g_back_buf; } +/* ================= 状态栏 MD3 图标 blit(写进本 SDK back_buf) ================= + * 复用 launcher sb_icons.h(Material Symbols rounded,32x32 单通道 alpha)作为状态栏 + * 右侧图标簇(「同一个状态栏」图标式,见 meow_ui_sys_statusbar_render)。 + * idx = sb_icons[] 索引(SB_I_*,见 meow_ui_internal.h) + * x = 图标槽左缘(槽宽 SB_ICON_SIZE=32) + * y = 图标槽顶(仅作参考;实际按 ink 垂直中心对齐 cy) + * ink_h = 目标 ink 高(>0 等比缩放;0 = 保持位图自然 ink 尺寸——WiFi 强弱档用) + * fg = 24-bit RGB 前景(主题 on_surface / on_surface_variant,日/夜由主题表提供) + * cy = ink 垂直中心线(= 状态栏文字中心,28px 栏约 14) + * 先求位图 ink 边界(阈值 32,忽略近透明抗锯齿),按 ink 在槽内水平居中、垂直中心对齐 cy; + * 像素按 alpha 软边混合进 back_buf(自 g_fb/g_line/g_xres/g_yres,与 launcher sb_blit_icon 同源算法)。 */ +void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy) +{ + (void)y; + if (!g_init_ok || !g_fb) return; + if (idx < 0 || idx >= (int)(sizeof sb_icons / sizeof sb_icons[0])) return; + const unsigned char *src = sb_icons[idx].data; + uint16_t col = rgb565(fg); + /* 1) ink 边界(含空图兜底) */ + int minx = SB_ICON_SIZE, miny = SB_ICON_SIZE, maxx = -1, maxy = -1; + for (int i = 0; i < SB_ICON_SIZE * SB_ICON_SIZE; i++) { + if (src[i] >= 32) { + int xx = i % SB_ICON_SIZE, yy = i / SB_ICON_SIZE; + if (xx < minx) minx = xx; if (xx > maxx) maxx = xx; + if (yy < miny) miny = yy; if (yy > maxy) maxy = yy; + } + } + if (maxx < minx || maxy < miny) { minx = miny = 0; maxx = maxy = SB_ICON_SIZE - 1; } + int ink_w = maxx - minx + 1, ink_h_n = maxy - miny + 1; + int t_h = (ink_h > 0) ? ink_h : ink_h_n; + int t_w = ink_w * t_h / ink_h_n; if (t_w < 1) t_w = 1; + if (t_w > SB_ICON_SIZE * 2) t_w = SB_ICON_SIZE * 2; /* 防御:等比极端上限 */ + /* 2) 目标框左上:水平居中于 32px 槽;垂直中心 = cy */ + int x0 = x + (SB_ICON_SIZE - t_w) / 2; + int y0 = cy - t_h / 2; + /* 3) 最近邻采样映射:目标 (dx,dy) ← 源 ink */ + for (int dy = 0; dy < t_h; dy++) { + int ty = y0 + dy; + if (ty < 0 || ty >= g_yres) continue; + int sy = miny + (dy * ink_h_n) / t_h; + uint16_t *row = (uint16_t *)(g_fb + (size_t)ty * g_line); + for (int dx = 0; dx < t_w; dx++) { + int tx = x0 + dx; + if (tx < 0 || tx >= g_xres) continue; + int sx = minx + (dx * ink_w) / t_w; + int a = src[sy * SB_ICON_SIZE + sx]; + if (a < 8) continue; /* 透明像素跳过 */ + uint16_t bg = row[tx]; + if (a >= 240) { row[tx] = col; continue; } + int a4 = a >> 4; /* 0..15 */ + int r = ((col >> 11) * a4 + ((bg >> 11) & 0x1F) * (16 - a4)) >> 4; + int g = (((col >> 5) & 0x3F) * a4 + ((bg >> 5) & 0x3F) * (16 - a4)) >> 4; + int b = ((col & 0x1F) * a4 + (bg & 0x1F) * (16 - a4)) >> 4; + row[tx] = (uint16_t)((r << 11) | (g << 5) | b); + } + } + /* 整条顶栏作为脏区标出(图标在顶条内,present 只重画顶条即可) */ + dirty_add(x, 0, SB_ICON_SIZE, MUI_SBAR_H); +} + /* ================= 像素写入 ================= */ static void px_write_rgb565(int x, int y, uint16_t c16) { @@ -663,7 +724,7 @@ static void present(void) * 顶条被 app 内容触及 / 上收动画未到位(逐帧推进)。展开态仍走 * meow_ui_sys_statusbar_render()(meow_ui_sys.c 不改,留给后续代理); * 上收折叠态(collapse_h>=MUI_SBAR_H)由基座只画提示线(内容上移出屏)。 */ - if (meow_ui_sys_statusbar_active() && + if (meow_ui_sys_render_enabled() && meow_ui_sys_statusbar_active() && (meow_ui_sys_statusbar_force() || statusbar_touched() || statusbar_collapse_in_transition())) { statusbar_collapse_step(); diff --git a/meow_ui_input.c b/meow_ui_input.c index c234ca7..80b32ae 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -380,12 +380,15 @@ void meow_ui_run(const meow_ui_events *ev) } /* A-0 全局状态栏 1s 轻刷:电量/WiFi/音量 + 时钟。时钟每秒变 → 重画顶条并上屏, - * 即使 APP 空闲(未主动 flush)也保持时钟走动。 */ + * 即使 APP 空闲(未主动 flush)也保持时钟走动。 + * 「同一个状态栏」闭环:仅系统进程(launcher)刷新顶条;App 进程不渲染状态栏, + * 此处不再 flush 顶条(避免把 launcher 叠画的系统顶条冲成 App 背景)。数据仍刷新 + * (meow_ui_sys_refresh 无副作用),App 侧 inset/热区不受影响。 */ static uint64_t last_sys_ms = 0; if (now - last_sys_ms >= 1000) { last_sys_ms = now; meow_ui_sys_refresh(); - if (meow_ui_sys_statusbar_active()) + if (meow_ui_sys_render_enabled() && meow_ui_sys_statusbar_active()) meow_ui_flush_region(0, 0, meow_ui_width(), MUI_SBAR_H); } diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 702a97b..67cb73b 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -19,7 +19,9 @@ typedef struct meow_ui_feed { int batt; /* 电量 %,-1 未知 */ int charging; /* 1 充电/满 0 放电 */ - int wifi_on; /* 1 网卡 up(operstate==up) */ + int wifi_on; /* 1 网卡 up(射频开) */ + int wifi_connected;/* 1 已关联 AP(operstate up);WiFi 图标分档用 */ + int wifi_level; /* 信号 dBm(<-1000=未知);≥-60 强 / -75 中 / 更弱弱 */ int bt_seen; /* hci0 节点存在 */ int bt_on; /* 实际上电 */ int vol; /* 音量 0-100 */ @@ -27,6 +29,55 @@ typedef struct meow_ui_feed { } meow_ui_feed; void meow_ui_sys_feed(const meow_ui_feed *f); /* 喂权威快照 + 置 force 重绘顶条 */ +/* ---- 系统 UI 模式(「同一个状态栏」闭环,§3.1 / FRAMEWORK_DESIGN §10.2)---- + * 状态栏 = 系统级组件,仅系统进程(launcher)渲染。SDK 在 App 进程与 launcher 进程都链接, + * 但顶条/提示线的**渲染**仅当本进程为系统 UI(launcher 置 system_mode=1)才进行;App 进程 + * 不渲染(meow_ui_sys_statusbar_active()/meow_ui_statusbar_height() 不受此开关影响 → App 仍 + * inset 28、热区可用,只是不画顶条、由 launcher 叠加)。launcher init 后调 + * meow_ui_sys_set_system_mode(1);App 保持默认 0。 */ +void meow_ui_sys_set_system_mode(int on); /* 1=本进程为系统 UI(渲染顶条/提示线) */ +int meow_ui_sys_render_enabled(void); /* 当前进程是否渲染系统顶条 */ +/* 把全局状态栏画进本进程 back_buf 顶部 MUI_SBAR_H 行(仅系统进程调用,见 render_enabled)。 + * launcher 的 App 前台叠加(launch_app 循环)直接调它 + 标脏顶条上屏。实现见 meow_ui_sys.c。 */ +void meow_ui_sys_statusbar_render(void); + +/* ---- 状态栏 MD3 图标簇(meow_ui_sys.c 渲染右侧;位图在 SDK 侧 sb_icons.h)---- + * meow_ui_gfx.c 的 meow_gfx_blit_icon(idx,...) 用这些索引查 sb_icons[]。 + * 索引值与 launcher sb_icons.h 的 SB_I_* 一一对应(位图同源,勿改序)。 */ +#define MEOW_SB_ICON_WIFI 0 +#define MEOW_SB_ICON_BT 1 +#define MEOW_SB_ICON_BATT 2 +#define MEOW_SB_ICON_VOL 3 +#define MEOW_SB_ICON_BRIGHT 4 +#define MEOW_SB_ICON_WIFI_STRONG 5 +#define MEOW_SB_ICON_WIFI_MEDIUM 6 +#define MEOW_SB_ICON_WIFI_WEAK 7 +#define MEOW_SB_ICON_WIFI_NO 8 +#define MEOW_SB_ICON_WIFI_OFF 9 +#define MEOW_SB_ICON_BT_NO 10 +#define MEOW_SB_ICON_BT_CONN 11 +#define MEOW_SB_ICON_BT_DIS 12 +#define MEOW_SB_ICON_BT_SEARCH 13 + +/* 状态栏右下布局常量(与 launcher SB_* 对齐):图标右缘↔文字间隙 / 相邻项间隙 / 图标目标 ink 高 / + * 右簇左缘相对时钟右缘安全间距 / 图标 ink 垂直中心(=状态栏文字中心,28px 栏约 14)。 */ +#define MEOW_SB_TGAP 6 +#define MEOW_SB_XGAP 10 +#define MEOW_SB_ICON_INK 16 +#define MEOW_SB_CLK_MARGIN 14 +#define MEOW_SB_ICON_CY 14 +#define MEOW_SB_ICON_SIZE 32 /* 图标槽宽(= sb_icons.h SB_ICON_SIZE,勿改序) */ + +/* 把第 idx 个 MD3 图标(sb_icons[])按 ink 等比 blit 进 back_buf;x=槽左缘、ink_h=目标 ink 高 + * (0=自然 ink,WiFi 强弱档用)、fg=24bit 前景、cy=ink 垂直中心线。见 meow_ui_gfx.c。 */ +void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy); + +/* ---- 顶级主题风格族(§8.1.2,minimal):状态栏图标集/配色随 ui.style(md3/miuix)---- + * meow_ui_theme.c 实现:读 settings.conf 的 ui.style(md3→0 / miuix→1),缓存,随 + * meow_theme_reload() 重读。launcher settings 改 ui.style 后经 sb_sdk_refresh→theme_reload + * →本值更新 → 状态栏图标集跟手切。公共版见 meow_ui.h(meow_ui_set_style/meow_ui_style)。 */ +int meow_ui_style(void); + /* ---- 主题(meow_ui_theme.c 提供)---- * 重置主题缓存,使下一次 meow_theme_get() 重读 settings.conf(display.theme / * display.theme_mode / display.theme_dark_start|end)。launcher 在 statusbar 刷新前调用, diff --git a/meow_ui_sys.c b/meow_ui_sys.c index 99d7d3b..ea70d87 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -35,6 +35,10 @@ static int g_sys_inited = 0; static int g_statusbar_on = 1; /* 默认显示 */ static int g_fullscreen = 0; /* 默认非全屏 */ static int g_statusbar_force = 0; /* 1s 刷新后置位,强制重画顶部条 */ +/* 「同一个状态栏」闭环(§3.1/FRAMEWORK_DESIGN §10.2):状态栏 = 系统级组件,仅系统进程 + * (launcher) 渲染。g_sys_render=1 表示本进程是系统 UI —— meow_ui_sys_statusbar_render / + * present() 据此决定是否画出顶条/提示线;App 进程保持 0(不渲染)+ 仍 inset 28(launcher 叠加)。 */ +static int g_sys_render = 0; /* 1=本进程为系统 UI(渲染);0=App(不渲染,仅 inset) */ static uint64_t now_ms(void) { @@ -129,7 +133,9 @@ void meow_ui_sys_refresh(void) char op[16]; read_str("/sys/class/net/wlan0/operstate", op, sizeof op); - g_sys.wifi_on = !strcmp(op, "up") ? 1 : 0; + g_sys.wifi_connected = !strcmp(op, "up") ? 1 : 0; + g_sys.wifi_on = g_sys.wifi_connected; /* SDK 自身读不到射频 vs 关联的区分,一起走 operstate */ + g_sys.wifi_level = -1000; /* 信号分档由 launcher 喂(唯一权威源);SDK 不读 /proc 无线 */ g_sys.vol = settings_vol(); g_sys.brightness = settings_bright(); /* 兜底读 settings;launcher 喂入会覆盖(更即时) */ @@ -159,18 +165,36 @@ const meow_sys_state *meow_ui_sys_get(void) { return &g_sys; } void meow_ui_sys_feed(const meow_ui_feed *f) { if (!f) return; - g_sys.batt = f->batt; - g_sys.charging = f->charging; - g_sys.wifi_on = f->wifi_on; - g_sys.bt_seen = f->bt_seen; - g_sys.bt_on = f->bt_on; - g_sys.vol = f->vol; + g_sys.batt = f->batt; + g_sys.charging = f->charging; + g_sys.wifi_on = f->wifi_on; + g_sys.wifi_connected = f->wifi_connected; + g_sys.wifi_level = f->wifi_level; + g_sys.bt_seen = f->bt_seen; + g_sys.bt_on = f->bt_on; + g_sys.vol = f->vol; if (f->brightness <= 0) g_sys.brightness = 0; /* 未知/屏关:不入顶条(>0 才显示) */ else if (f->brightness > 100) g_sys.brightness = 100; else g_sys.brightness = f->brightness; g_statusbar_force = 1; /* 数据已变 → 顶条必重画 */ } +/* ---- 系统 UI 模式(「同一个状态栏」闭环;launcher 调用)---- + * launcher(系统进程)链 meow_ui.a 后调 meow_ui_sys_set_system_mode(1):本进程渲染全局状态栏 + * (present() 前置合成 + App 前台叠加)。App 进程默认 0:不渲染状态栏、只 inset(§6.1 ③ / + * FRAMEWORK_DESIGN §10.2)。meow_ui_sys_statusbar_active()/meow_ui_statusbar_height() 不随此 + * 开关变(App 仍报告 28px inset + 顶部热区),仅渲染路径受控。 */ +void meow_ui_sys_set_system_mode(int on) +{ + g_sys_render = on ? 1 : 0; + g_statusbar_force = 1; /* 切换即强制下次 present 反映(画/不画顶条) */ +} + +int meow_ui_sys_render_enabled(void) +{ + return g_sys_render; +} + /* ---- 公共 API ---- */ void meow_ui_set_statusbar(int visible) { @@ -225,35 +249,50 @@ int meow_ui_sys_statusbar_force(void) return f; } -/* 状态栏右侧信息串:batt% WIFI 蓝牙 VOL nn% 亮度nn%(同 launcher 状态栏右侧语义,文本式)。 - * mfont 就绪用 CJK「蓝牙/亮度」,否则回退 ASCII "BT/BRI"(28px 栏紧凑,避免超宽叠到时钟)。 - * 顺序与 launcher 右侧簇一致:电量 → WiFi → 蓝牙 → 音量 → 亮度。*/ -static void statusbar_right_str(char *out, size_t n) +/* ---- 右侧信息簇(MD3 图标 + 变量值,取代文本式 statusbar_right_str)---- + * 「同一个状态栏」右侧 = 图标+值:电量图标+% / 音量图标+% / 蓝牙图标 / WiFi 图标(信号分档)/ + * 亮度图标+%(可选,与时钟不重叠才画)。图标位图来自 sb_icons.h(复用 launcher); + * 文字=紧凑数值(%d%%),无 CJK 依赖;颜色随 meow_theme(日/夜)由主题表提供。 + * 顺序(右→左):电量(最右)→ 音量 → 蓝牙 → WiFi → 亮度。 */ +/* 注:旧文本式右侧串 statusbar_right_str 已退役为图标簇(同上),不再保留调用路径。 */ + +/* 画一个右对齐项:右边界=*xr,画完向左推进 *xr(含 MEOW_SB_XGAP 项间隙)。 + * icon>=0 画图标(可配右侧数值 txt;txt==NULL 仅图标)。文字/图标同用主题色 col。 + * ink_h 传给 meow_gfx_blit_icon(主图标用 MEOW_SB_ICON_INK 等大;WiFi 强弱档传 0 保持自然 ink)。 + * 返回该项左边缘(供亮度可选判宽)。 */ +static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty) { - char batt[16] = "", wifi[16] = "", vol[20] = "", bt[16] = "", bri[20] = ""; - if (g_sys.batt >= 0) snprintf(batt, sizeof batt, "%d%%", g_sys.batt); - if (g_sys.wifi_on) snprintf(wifi, sizeof wifi, "WIFI"); - snprintf(vol, sizeof vol, "VOL %d%%", g_sys.vol); - if (g_sys.bt_seen && g_sys.bt_on) - snprintf(bt, sizeof bt, meow_gfx_mfont_ready() ? "蓝牙" : "BT"); - if (g_sys.brightness > 0) /* >0 才显示(屏常亮态);0=屏关/未知不入 */ - snprintf(bri, sizeof bri, meow_gfx_mfont_ready() ? "亮度%d%%" : "BRI %d%%", g_sys.brightness); - out[0] = 0; - if (batt[0]) strcat(out, batt); - if (wifi[0]) { if (out[0]) strcat(out, " "); strcat(out, wifi); } - if (bt[0]) { if (out[0]) strcat(out, " "); strcat(out, bt); } - if (vol[0]) { if (out[0]) strcat(out, " "); strcat(out, vol); } - if (bri[0]) { if (out[0]) strcat(out, " "); strcat(out, bri); } + int iconw = (icon >= 0) ? MEOW_SB_ICON_SIZE : 0; + int gap = (icon >= 0 && txt) ? MEOW_SB_TGAP : 0; + int tw = txt ? meow_ui_text_width(txt, MUI_SBAR_FONT) : 0; + int x = *xr - (iconw + gap + tw); + if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, MEOW_SB_ICON_CY); x += iconw + gap; } + if (txt) meow_ui_text_right(*xr, ty, txt, MUI_SBAR_FONT, col); + *xr -= (iconw + gap + tw) + MEOW_SB_XGAP; + return *xr + MEOW_SB_XGAP; +} + +/* WiFi 信号分档(g_sys.wifi_connected + wifi_level)→ 图标索引。 + * 网卡关 → -1(不画);开未关联 → SB_I_WIFI_NO;已关联 → 按 dBm 分 STRONG/MEDIUM/WEAK(未知→满格)。 */ +static int sb_wifi_icon(void) +{ + if (!g_sys.wifi_on) return MEOW_SB_ICON_WIFI_OFF; + if (!g_sys.wifi_connected) return MEOW_SB_ICON_WIFI_NO; + if (g_sys.wifi_level <= -1000) return MEOW_SB_ICON_WIFI_STRONG; /* 未知 → 满格 */ + if (g_sys.wifi_level >= -60) return MEOW_SB_ICON_WIFI_STRONG; + if (g_sys.wifi_level >= -75) return MEOW_SB_ICON_WIFI_MEDIUM; + return MEOW_SB_ICON_WIFI_WEAK; } void meow_ui_sys_statusbar_render(void) { + if (!g_sys_render) return; /* 仅系统进程(launcher)渲染;App 不画顶条 */ if (!meow_ui_sys_statusbar_active()) return; const meow_theme *th = meow_theme_get(); int w = meow_ui_width(); int h = MUI_SBAR_H; - /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶) */ + /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程(launcher)渲染本函数。 */ meow_ui_fill_rect(0, 0, w, h, th->bg); /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */ @@ -268,11 +307,32 @@ void meow_ui_sys_statusbar_render(void) snprintf(timebuf, sizeof timebuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); meow_ui_text(8, ty, timebuf, MUI_SBAR_FONT, th->fg); - /* 右侧信息串:batt% WIFI 蓝牙 VOL nn% */ - char right[96]; - statusbar_right_str(right, sizeof right); - if (right[0]) - meow_ui_text_right(w - 8, ty, right, MUI_SBAR_FONT, th->fg); + /* 右侧 MD3 图标簇(从右端 w-8 往左)。主项色 th->fg;次级(蓝牙/弱信号)用 on_surface_variant。 + * 亮度「可选」:仅在右簇左缘与时钟右缘相距足够(MEOW_SB_CLK_MARGIN)时画,避免与时钟叠字。 */ + uint32_t col = th->fg, col2 = th->dim; /* col=主色;col2=次要色(蓝牙/分隔用 on_surface_variant=dim 近似) */ + int xr = w - 8; + char val[16]; + if (g_sys.batt >= 0) { /* 电量:图标+%(未知不画)——最右项 */ + snprintf(val, sizeof val, "%d%%", g_sys.batt); + sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty); + } + snprintf(val, sizeof val, "%d%%", g_sys.vol); + sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty); /* 音量:图标+% */ + if (g_sys.bt_seen && g_sys.bt_on) { /* 蓝牙:仅图标(上电即显示,连接态占位;多态留后续) */ + sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty); + } + if (g_sys.wifi_on) { /* WiFi:仅图标(关不画;开未连/分档) */ + int wco = sb_wifi_icon(); + int is_weak = (wco == MEOW_SB_ICON_WIFI_MEDIUM || wco == MEOW_SB_ICON_WIFI_WEAK); + sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty); + } + if (g_sys.brightness > 0) { /* 亮度:图标+%(>0 才显示;与时钟不重叠才画) */ + int bl = (g_sys.brightness > 100) ? 100 : g_sys.brightness; + snprintf(val, sizeof val, "%d%%", bl); + int bw = MEOW_SB_ICON_SIZE + MEOW_SB_TGAP + meow_ui_text_width(val, MUI_SBAR_FONT) + MEOW_SB_XGAP; + if (xr - bw > 8 + meow_ui_text_width(timebuf, MUI_SBAR_FONT) + MEOW_SB_CLK_MARGIN) + sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty); + } /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */ meow_ui_fill_rect(0, h - 1, w, 1, th->dim); diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 070e61b..3a58d6b 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -23,7 +23,9 @@ typedef struct meow_sys_state { int batt; /* 电量 %;-1=未知 */ int charging; /* 1=充电/满 */ - int wifi_on; /* operstate==up */ + int wifi_on; /* 网卡 up(射频开;launcher 喂入按 g_sys.wifi_on) */ + int wifi_connected;/* 已关联 AP(operstate up);launcher 喂入(g_sys.wifi_connected)。WiFi 分档用 */ + int wifi_level; /* 信号 dBm(-1000=未知);launcher 喂入(-60 强 / -75 中 / 更弱弱)。 */ int bt_seen; /* hci0 节点存在 */ int bt_on; /* 实际上电(hciconfig UP) */ int vol; /* 音量 0-100 */ diff --git a/meow_ui_theme.c b/meow_ui_theme.c index c567ee8..8a34936 100644 --- a/meow_ui_theme.c +++ b/meow_ui_theme.c @@ -31,6 +31,7 @@ static meow_theme_mode g_manual = MEOW_THEME_DARK; /* display.theme 解析出的 static int g_dark_start = 0; /* 深色时段开始(分钟 0-1439) */ static int g_dark_end = 0; /* 深色时段结束(分钟 0-1439) */ static int g_loaded = 0; +static int g_style = MEOW_STYLE_MD3; /* 顶级主题风格族(§8.1.2):默认 MD3 */ static meow_theme_listener g_listeners[8]; static int g_nlisteners = 0; @@ -107,6 +108,15 @@ static void load_settings(void) get_setting_int("display.theme_dark_start", &g_dark_start); get_setting_int("display.theme_dark_end", &g_dark_end); + + /* 顶级主题风格族(§8.1.2 minimal):ui.style 的 md3/miuix。读取缺失/未知 → 回退 MD3。 + * 决定状态栏右侧图标集(§3.1 同一个状态栏);配色日/夜仍由 display.theme 现有解析管。 */ + g_style = MEOW_STYLE_MD3; + if (get_setting_str("ui.style", buf, sizeof buf) == 0) { + const char *v = buf; + while (*v == ' ' || *v == '\t') v++; + if (!strncmp(v, "miuix", 5) || !strncmp(v, "miui", 4)) g_style = MEOW_STYLE_MIUIX; + } } /* 当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用(与 launcher s_now_min_of_day 一致)。 */ @@ -186,6 +196,28 @@ meow_theme meow_theme_defaults(meow_theme_mode m) } } +/* ---- §8.1.2 顶级主题风格族(minimal)---- + * meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读, + * 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。 + * meow_ui_set_style() 记录 style(越界 clamp;同值忽略)并触发 reload/broadcast——供需要 + * 显式切换的调用方(后续 launcher 设置页切换可改走此路径);状态栏图标集据此换集。 */ +int meow_ui_style(void) +{ + if (!g_loaded) { load_settings(); g_loaded = 1; } + return g_style; +} + +void meow_ui_set_style(int style) +{ + if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3; + if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; + if (style == g_style) return; + g_style = style; + /* 触发下一帧按新 style 重绘:清 settings 缓存(下次 get 重读)不必要(style 已记录), + * 仅广播主题 listener(沿用 theme 广播机制,让控件/状态栏下一帧用新 style 重绘)。 */ + broadcast(); +} + void meow_theme_add_listener(meow_theme_listener cb) { if (!cb) return; diff --git a/render_sb_statusbar.c b/render_sb_statusbar.c new file mode 100644 index 0000000..90e213b --- /dev/null +++ b/render_sb_statusbar.c @@ -0,0 +1,51 @@ +/* render_sb_statusbar.c — 宿主可视化渲染:把「同一个状态栏」画进 back_buf 并 dump PPM 目检。 + * 仅用于宿主验证(无 /dev/fb0 → 480x720 离屏;字体缺失时图标仍画,数值可能缺字)。 + * 编译(宿主 gcc): + * gcc -o render_sb_statusbar render_sb_statusbar.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c \ + * meow_ui_vol.c meow_ui_log.c /tmp/meow_font_host.o -I. -I../meow_launcherd -lz + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" +#include +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + meow_ui_sys_set_system_mode(1); + meow_ui_set_statusbar(1); + + /* 喂权威快照(模拟 launcher):电量81/充电/音量60/蓝牙连/WiFi已连-50(强)/亮度100 */ + meow_ui_feed f; + memset(&f, 0, sizeof f); + f.batt = 81; f.charging = 1; f.wifi_on = 1; f.wifi_connected = 1; f.wifi_level = -50; + f.bt_seen = 1; f.bt_on = 1; f.vol = 60; f.brightness = 100; + meow_ui_sys_feed(&f); + + /* 清整屏(背景)→ 画状态栏 → 把 top 28 行 + 一点内容区 dump 成 PPM */ + meow_ui_clear(0x141824); + meow_ui_sys_statusbar_render(); + + const char *path = argv[1] ? argv[1] : "/tmp/sb_statusbar.ppm"; + FILE *fp = fopen(path, "wb"); + if (!fp) { printf("cannot open %s\n", path); return 1; } + int w = 480, h = 480; /* 宽 x 全屏高,看顶条+背景 */ + fprintf(fp, "P6\n%d %d\n255\n", w, h); + unsigned char *bb = meow_ui_back_buf(); + for (int ry = 0; ry < h; ry++) { + for (int cx = 0; cx < w; cx++) { + uint16_t p = *(uint16_t *)(bb + (size_t)ry * (size_t)(w * 2) + (size_t)cx * 2); + int r = ((p >> 11) & 0x1F) << 3; + int g = ((p >> 5) & 0x3F) << 2; + int b = (p & 0x1F) << 3; + fputc(r, fp); fputc(g, fp); fputc(b, fp); + } + } + fclose(fp); + printf("wrote %s (%dx%d)\n", path, w, h); + return 0; +} diff --git a/sb_icons.h b/sb_icons.h new file mode 100644 index 0000000..c082b26 --- /dev/null +++ b/sb_icons.h @@ -0,0 +1,216 @@ +/* sb_icons.h — 状态栏/通知中心图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。 + * 生成自 material-symbols@0.47.0 rounded 字体光栅化;每像素 0..255(亮度/alpha)。 + * ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。 */ +#ifndef SB_ICONS_H +#define SB_ICONS_H + +#define SB_ICON_SIZE 32 + +typedef struct { const unsigned char *data; } sb_icon_t; + +static const unsigned char SB_ICON_WIFI[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,3,3,2,2,3,3,4,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,0,30,77,122,154,177,187,187,177,154,122,77,30,0,0,0,3,1,0,0,0,0,0,0,0,0,0,3,0,0,25,108,191,244,255,255,255,255,255,255,255,255,255,255,244,191,108,25,0,0,3,0,0,0,0,0,0,1,1,0,27,140,235,255,255,255,249,248,251,254,255,255,254,251,248,249,255,255,255,235,140,27,0,1,1,0,0,0,2,0,0,110,235,255,255,249,251,255,255,255,255,246,240,240,246,255,255,255,255,251,249,255,255,235,110,0,0,2,0,1,0,25,181,255,255,249,253,255,255,216,151,92,53,29,18,18,29,53,92,151,217,255,255,253,249,255,255,181,25,0,1, +1,30,220,255,250,251,255,255,180,74,6,0,0,0,0,0,0,0,0,0,0,6,75,180,255,255,251,250,255,220,30,1,0,124,255,244,252,255,207,69,0,0,0,6,7,4,0,0,0,0,4,7,6,0,0,0,69,207,255,252,244,255,123,0,0,76,255,255,255,147,8,0,3,6,0,0,0,0,7,15,15,7,0,0,0,0,6,3,0,8,147,255,255,255,76,0,1,0,93,151,80,0,0,5,1,0,2,67,140,193,221,235,235,221,192,138,64,0,0,1,5,0,0,80,151,93,0,1,0,0,0,0,0,2,2,0,0,97,207,255,255,255,255,255,255,255,255,255,255,204,93,0,0,2,2,0,0,0,0,0,0,0,3,4,3,2,0,37,191,255,255,251,248,255,255,255,255,255,254,248,251,255,255,187,35,0,2,3,4,3,0,0,0,0,0,0,1,1,7,212,255,248,251,255,255,246,213,193,193,214,247,255,255,250,248,255,211,7,1,1,0,0,0,0,0,0,0,0,1,0,10,235,255,255,255,197,98,31,3,0,0,3,33,100,200,255,255,255,234,10,0,1,0,0,0,0, +0,0,0,0,0,2,0,85,216,206,95,0,0,0,1,6,6,1,0,0,0,99,208,215,81,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,3,0,0,0,0,3,3,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,3,0,1,0,48,119,107,25,0,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,2,0,95,251,255,255,230,48,0,2,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,21,241,255,249,248,255,190,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,63,255,252,255,254,253,235,14,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,46,255,251,253,251,252,225,7,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,185,255,255,255,255,124,0,3,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,0,20,163,231,223,128,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BT[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,9,153,156,6,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,3,0,61,255,255,183,8,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,3,0,66,252,247,255,186,10,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,2,3,0,65,255,253,255,255,188,11,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,44,221,195,23,0,6,0,65,255,251,227,255,255,191,13,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,85,255,255,208,23,0,1,65,255,254,69,167,255,255,193,16,2,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,5,159,255,255,209,25,0,69,255,255,63,0,175,252,255,165,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,160,255,255,212,17,55,255,255,57,16,197,251,255,150,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,159,255,255,198,88,254,254,88,198,255,255,171,6,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,158,255,255,244,254,254,244,255,255,167,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,158,255,255,255,255,255,255,164,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,153,251,253,253,251,156,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,18,235,254,254,236,19,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,21,204,254,253,253,253,204,20,0,3,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,20,204,255,255,255,255,255,255,203,19,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,21,205,255,255,214,254,254,214,255,255,203,19,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,21,206,255,255,151,60,255,255,60,151,255,255,203,21,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,22,206,255,255,167,0,63,255,255,66,0,169,253,255,170,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,1,21,203,255,255,163,1,1,67,255,255,54,25,206,251,255,148,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,92,255,255,162,0,4,0,65,255,253,101,211,255,255,176,6,2,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,22,169,140,0,1,5,0,65,255,252,252,255,255,178,5,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,65,255,253,251,255,180,5,1,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,1,3,3,1,0,3,0,66,252,247,255,182,6,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,60,255,255,183,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,8,151,157,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BATT[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,16,16,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,7,4,75,235,237,237,235,75,4,7,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,255,255,255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,80,123,115,184,253,251,251,253,184,116,123,98,8,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,49,255,255,255,255,255,255,255,255,255,255,255,255,107,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,68,253,249,239,236,239,238,238,239,236,236,246,252,131,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,64,255,255,77,16,19,17,17,17,16,32,235,255,125,0,4,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,3,0,65,255,255,62,0,0,0,0,0,0,13,234,255,126,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,255,66,1,4,1,1,2,1,18,234,255,126,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,255,67,2,5,2,2,3,2,19,234,255,126,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,255,56,0,0,0,0,0,0,6,233,255,126,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,254,206,190,190,190,190,190,190,194,247,255,127,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,252,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,253,252,252,252,252,252,252,252,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,65,255,253,255,255,255,255,255,255,255,255,251,255,128,0,4,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,3,0,64,255,253,255,255,255,255,255,255,255,255,251,255,127,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,67,253,249,251,251,251,251,251,251,251,251,247,251,131,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,51,255,255,255,255,255,255,255,255,255,255,255,255,111,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,88,128,126,128,128,128,128,128,128,127,127,110,11,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_VOL[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,2,1,1,20,193,209,94,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,0,48,255,255,255,178,21,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,53,198,127,1,3,2,104,229,255,255,211,24,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,58,239,255,245,15,0,2,0,23,172,255,255,199,7,3,1,0,0,0,0,0,0,0,0,0,3,3,3,3,5,0,57,239,255,252,236,17,0,0,3,0,0,161,255,255,131,0,3,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,58,239,255,255,255,238,17,0,4,3,0,3,5,206,254,245,32,0,2,0,0,0,0,0,0,0,0,51,66,65,66,88,238,255,255,227,255,238,20,0,110,209,24,3,0,64,253,255,130,0,4,0,0,0,0,0,3,0,87,255,255,255,255,255,255,255,111,117,255,234,21,0,130,255,187,2,2,0,198,254,205,2,1,0,0,0,0,0,4,0,133,255,249,255,255,255,254,122,0,127,255,234,21,0,128,253,255,78,0,0,125,255,242,28,0,2,0,0,0,0,4,0,125,255,238,72,58,62,49,0,2,127,255,234,21,0,129,255,254,159,0,0,83,255,255,55,0,3,0,0,0,0,4,0,126,255,233,2,0,0,0,9,0,126,255,234,21,0,129,255,255,187,0,0,67,255,255,64,0,3,0,0,0,0,4,0,125,255,235,31,15,17,9,0,2,126,255,234,21,0,129,255,254,173,0,0,76,255,255,59,0,3,0,0,0,0,4,0,131,251,245,228,226,227,220,71,0,131,255,234,21,0,129,254,255,108,0,1,112,255,247,36,0,2,0,0, +0,0,4,0,111,255,255,255,255,255,255,249,61,111,255,234,21,0,127,255,225,17,1,0,178,255,217,6,0,1,0,0,0,0,1,0,11,110,127,126,126,148,255,255,240,197,255,237,21,0,127,249,61,0,2,37,245,255,153,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,106,255,255,255,255,238,18,0,25,41,0,6,0,173,255,254,54,0,3,0,0,0,0,0,0,0,1,4,4,4,4,5,0,106,255,250,254,236,17,0,0,0,5,0,112,255,255,169,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,106,255,255,245,17,0,3,0,0,117,254,255,232,26,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,102,243,176,3,3,0,53,184,255,255,244,60,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,26,4,1,1,38,251,255,255,224,60,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,35,238,246,153,23,0,3,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,1,0,31,39,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BRIGHT[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,14,64,64,32,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,4,6,0,36,212,255,255,240,77,0,5,4,4,4,3,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,47,226,255,255,254,255,251,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,67,125,126,127,131,234,255,255,118,73,248,255,253,147,126,126,126,89,2,0,1,0,0,0,0,0,0,0,0,3,0,106,255,255,255,255,255,255,255,119,0,0,71,243,255,255,255,255,255,255,162,2,2,0,0,0,0,0,0,0,1,0,7,227,254,237,226,227,225,228,111,0,8,9,0,64,218,227,226,226,232,252,255,46,0,2,0,0,0,0,0,0,1,0,17,234,255,132,12,16,13,14,0,0,0,0,0,0,9,14,15,12,74,255,254,67,0,3,0,0,0, +0,0,0,1,0,18,234,255,124,0,1,0,0,15,77,119,124,89,31,0,0,1,0,63,255,255,66,0,3,0,0,0,0,0,0,1,1,14,233,255,126,1,7,0,98,224,255,255,255,255,240,135,5,4,1,65,255,255,61,2,3,0,0,0,0,0,0,3,0,30,241,253,130,0,0,125,255,255,252,251,251,252,255,255,177,6,1,69,254,255,85,0,5,0,0,0,0,0,2,0,21,204,253,255,116,0,78,255,251,253,255,255,255,255,254,249,255,137,0,57,255,253,234,58,0,3,0,0,0,1,0,23,206,255,255,169,10,3,203,254,252,255,255,255,255,255,255,254,252,246,29,0,118,255,255,240,59,0,2,0,0,2,5,194,255,255,173,2,0,37,251,255,254,255,255,255,255,255,255,255,252,255,92,0,0,119,255,255,238,31,1,1,3,0,57,255,253,176,0,6,1,63,255,253,255,255,255,255,255,255,255,255,251,254,124,0,9,0,120,253,255,119,0,5,2,0,45,255,253,204,28,0,1,59,255,254,255,255,255,255,255,255,255,255,251,254,120,1,5,5,162,253,255,102,0,4, +0,2,0,151,255,255,216,27,0,27,245,254,254,255,255,255,255,255,255,255,252,255,78,0,3,171,255,255,204,13,1,1,0,1,2,1,160,255,255,213,33,0,179,255,251,255,255,255,255,255,255,253,253,231,16,10,169,255,255,206,22,0,1,0,0,0,1,2,0,158,252,255,131,0,47,246,255,251,254,255,255,254,252,251,255,96,0,67,255,252,202,21,0,2,0,0,0,0,0,2,0,12,236,253,126,0,0,76,244,255,255,253,253,255,255,255,126,0,1,66,253,255,64,0,5,0,0,0,0,0,0,1,1,18,233,255,128,4,11,0,50,175,241,255,255,248,198,81,0,9,4,68,255,255,65,1,3,0,0,0,0,0,0,1,0,18,233,255,117,0,0,0,0,0,28,59,63,37,0,0,0,0,0,51,255,254,65,0,3,0,0,0,0,0,0,1,0,15,237,252,184,116,119,117,118,24,0,0,0,0,0,107,119,118,116,152,253,255,65,0,3,0,0,0,0,0,0,0,2,2,192,255,255,255,255,255,255,208,26,0,6,3,163,255,255,255,255,255,255,241,24,0,1,0,0,0, +0,0,0,0,2,0,34,187,234,234,234,237,255,255,212,24,0,169,255,255,243,234,234,234,209,70,0,2,0,0,0,0,0,0,0,0,0,0,0,0,16,17,18,20,159,255,255,207,175,255,255,205,35,18,17,17,1,0,1,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,159,255,255,255,255,206,21,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,2,0,151,252,255,190,22,0,3,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,45,56,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_WIFI_STRONG[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,3,4,3,3,2,2,3,3,4,3,2,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0, +0,0,0,0,0,1,3,0,0,0,30,77,122,154,177,187,187,177,154,122,77,30,0,0,0,3,1,0,0,0,0,0, +0,0,0,0,3,0,0,25,108,191,244,255,255,255,255,255,255,255,255,255,255,244,191,108,25,0,0,3,0,0,0,0, +0,0,1,1,0,27,140,235,255,255,255,249,248,251,254,255,255,254,251,248,249,255,255,255,235,140,27,0,1,1,0,0, +0,2,0,0,110,235,255,255,249,251,255,255,255,255,246,240,240,246,255,255,255,255,251,249,255,255,235,110,0,0,2,0, +1,0,25,181,255,255,249,253,255,255,216,151,92,53,29,18,18,29,53,92,151,217,255,255,253,249,255,255,181,25,0,1, +1,30,220,255,250,251,255,255,180,74,6,0,0,0,0,0,0,0,0,0,0,6,75,180,255,255,251,250,255,220,30,1, +0,124,255,244,252,255,207,69,0,0,0,6,7,4,0,0,0,0,4,7,6,0,0,0,69,207,255,252,244,255,123,0, +0,76,255,255,255,147,8,0,3,6,0,0,0,0,7,15,15,7,0,0,0,0,6,3,0,8,147,255,255,255,76,0, +1,0,93,151,80,0,0,5,1,0,2,67,140,193,221,235,235,221,192,138,64,0,0,1,5,0,0,80,151,93,0,1, +0,0,0,0,0,2,2,0,0,97,207,255,255,255,255,255,255,255,255,255,255,204,93,0,0,2,2,0,0,0,0,0, +0,0,3,4,3,2,0,37,191,255,255,251,248,255,255,255,255,255,254,248,251,255,255,187,35,0,2,3,4,3,0,0, +0,0,0,0,1,1,7,212,255,248,251,255,255,246,213,193,193,214,247,255,255,250,248,255,211,7,1,1,0,0,0,0, +0,0,0,0,1,0,10,235,255,255,255,197,98,31,3,0,0,3,33,100,200,255,255,255,234,10,0,1,0,0,0,0, +0,0,0,0,0,2,0,85,216,206,95,0,0,0,1,6,6,1,0,0,0,99,208,215,81,0,2,0,0,0,0,0, +0,0,0,0,0,0,0,0,4,0,0,0,4,3,0,0,0,0,3,3,0,0,1,3,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,2,0,0,3,0,1,0,48,119,107,25,0,1,0,3,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,1,0,2,0,95,251,255,255,230,48,0,2,0,1,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,21,241,255,249,248,255,190,1,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,63,255,252,255,254,253,235,14,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,2,0,46,255,251,253,251,252,225,7,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,2,2,185,255,255,255,255,124,0,3,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,0,20,163,231,223,128,2,1,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_WIFI_MEDIUM[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,3,4,2,2,2,2,2,2,4,3,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,2,0,0,3,68,141,187,189,189,189,189,186,139,65,1,0,1,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,1,0,1,99,209,255,255,255,255,255,255,255,255,255,255,205,95,0,0,1,0,0,0,0,0,0, +0,0,0,0,0,2,0,39,194,255,255,251,248,255,255,255,255,255,254,248,251,255,255,190,37,0,2,0,0,0,0,0, +0,0,0,0,1,1,7,215,255,248,251,255,255,246,213,193,193,213,247,255,255,251,248,255,213,7,1,1,0,0,0,0, +0,0,0,0,1,0,10,233,255,255,255,194,96,31,3,0,0,3,32,99,197,255,255,255,232,9,0,1,0,0,0,0, +0,0,0,0,0,2,0,80,210,199,89,0,0,0,1,6,6,1,0,0,0,93,202,209,76,0,2,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,3,3,0,0,0,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,2,0,0,3,0,1,0,25,107,119,47,0,1,0,3,0,0,2,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,1,0,0,2,0,48,230,255,255,251,94,0,2,0,0,1,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,2,1,191,255,248,249,255,241,20,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,14,236,253,254,255,252,255,63,0,3,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,0,7,226,252,251,253,251,255,46,0,2,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,3,0,124,255,255,255,255,184,2,2,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1,2,128,223,231,162,19,0,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_WIFI_WEAK[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1,0,4,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,2,0,52,213,237,238,184,19,1,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,10,219,255,255,255,255,160,0,3,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,57,255,252,254,252,252,232,11,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,3,0,57,255,252,255,253,252,232,11,0,1,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1,1,10,219,255,251,251,255,160,0,3,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,2,0,52,215,255,255,185,20,1,1,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1,0,9,59,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +/* 网络状态位图(M2.48):WiFi 开未连接/关闭、蓝牙 未连接/已连接/关闭/搜索中 */ +static const unsigned char SB_ICON_WIFI_NO[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,3,3,2,2,3,3,4,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,0,30,77,122,154,177,187,187,177,154,122,77,30,0,0,0,3,1,0,0,0,0,0,0,0,0,0,3,0,0,25,108,191,244,255,255,255,255,255,255,255,255,255,255,244,191,108,25,0,0,3,0,0,0,0,0,0,1,1,0,27,140,235,255,255,255,249,248,251,254,255,255,254,251,248,249,255,255,255,235,140,27,0,1,1,0,0,0,2,0,0,110,235,255,255,249,251,255,255,255,255,246,240,240,246,255,255,255,255,251,249,255,255,235,110,0,0,2,0,1,0,25,181,255,255,249,253,255,255,216,151,92,53,29,18,18,29,53,92,151,217,255,255,253,249,255,255,181,25,0,1, +1,30,220,255,250,251,255,255,180,74,6,0,0,0,0,0,0,0,0,0,0,6,75,180,255,255,251,250,255,220,30,1,0,124,255,244,252,255,207,69,0,0,0,6,7,4,0,0,0,0,4,7,6,0,0,0,69,207,255,252,244,255,123,0,0,76,255,255,255,147,8,0,3,6,0,0,0,0,7,15,15,7,0,0,0,0,6,3,0,8,147,255,255,255,76,0,1,0,93,151,80,0,0,5,1,0,2,67,140,193,221,235,235,221,192,138,64,0,0,1,5,0,0,80,151,93,0,1,0,0,0,0,0,2,2,0,0,97,207,255,255,255,255,255,255,255,255,255,255,204,93,0,0,2,2,0,0,0,0,0,0,0,3,4,3,2,0,37,191,255,255,251,248,255,255,255,255,255,254,248,251,255,255,187,35,0,2,3,4,3,0,0,0,0,0,0,1,1,7,212,255,248,251,255,255,246,213,193,193,214,247,255,255,250,248,255,211,7,1,1,0,0,0,0,0,0,0,0,1,0,10,235,255,255,255,197,98,31,3,0,0,3,33,100,200,255,255,255,234,10,0,1,0,0,0,0, +0,0,0,0,0,2,0,85,216,206,95,0,0,0,1,6,6,1,0,0,0,99,208,215,81,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,3,0,0,0,0,3,3,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,3,0,1,0,48,119,107,25,0,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,2,0,95,251,255,255,230,48,0,2,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,21,241,255,249,248,255,190,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,63,255,252,255,254,253,235,14,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,46,255,251,253,251,252,225,7,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,185,255,255,255,255,124,0,3,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,1,0,20,163,231,223,128,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_WIFI_OFF[1024]={ +0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,3,3,3,3,3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,22,55,0,1,1,0,0,0,2,0,0,0,0,0,0,0,0,1,4,3,1,0,0,0,0,0,0,0,0,1,1,6,208,255,135,0,3,1,0,1,0,14,54,65,65,64,56,39,16,0,0,0,0,3,2,0,0,0,0,0,0,1,1,6,207,255,255,137,0,4,3,1,54,225,255,255,255,255,255,251,235,201,147,77,13,0,0,3,1,0,0,0,0,0,2,0,34,212,252,255,136,0,6,0,171,255,247,249,248,248,250,253,255,255,255,255,224,135,35,0,0,3,0,0,0,0,0,5,0,56,247,251,255,135,0,1,134,255,255,255,255,255,255,255,255,255,248,250,255,255,242,141,20,0,3,0,0,0,1,0,92,226,253,253,248,255,135,0,13,139,177,147,132,129,142,170,205,245,255,255,254,248,255,255,225,84,0,0,0, +2,5,143,255,255,253,254,255,255,255,133,0,0,0,0,0,0,0,0,0,30,91,179,252,255,253,249,255,255,140,4,2,0,98,255,249,250,255,255,175,217,255,255,132,0,7,4,4,4,4,4,3,0,0,0,48,164,255,255,250,250,255,96,0,0,109,255,255,255,225,85,0,37,228,255,255,130,0,4,1,0,0,0,0,0,7,3,0,0,70,217,255,254,255,113,0,1,14,176,234,168,26,0,6,0,34,212,252,255,128,0,4,2,0,34,53,3,0,0,5,4,0,18,160,233,185,18,1,0,0,0,15,0,0,4,0,0,21,163,254,249,255,127,0,7,0,143,255,211,127,23,0,1,3,0,0,14,0,0,0,0,1,1,0,2,2,0,6,121,239,255,254,252,247,255,126,0,4,17,190,255,255,231,104,0,1,2,2,0,1,1,0,0,0,0,1,1,2,7,181,255,255,250,254,255,255,255,255,125,0,0,12,190,255,254,255,155,3,2,0,1,0,0,0,0,0,0,0,2,0,33,255,249,251,255,255,196,131,229,255,255,123,0,2,13,193,255,254,255,32,0,2,0,0,0,0, +0,0,0,0,1,2,5,183,255,255,191,63,0,0,48,234,255,255,122,0,1,14,183,249,173,8,2,1,0,0,0,0,0,0,0,0,0,1,0,10,89,72,0,0,1,7,0,46,232,255,255,121,0,1,0,35,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,6,56,221,255,255,119,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,3,0,1,0,19,162,234,211,177,234,255,255,118,0,6,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,184,255,255,255,255,173,228,255,255,117,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,46,255,251,253,251,255,209,57,236,255,255,116,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,63,255,252,255,254,253,241,0,54,240,255,255,115,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,20,240,255,249,248,255,188,4,0,59,240,255,255,114,0,2,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,2,0,92,250,255,255,228,46,0,6,0,61,241,255,255,109,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,47,119,107,24,0,1,0,4,0,63,242,255,255,31,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,2,0,0,0,4,0,63,224,185,13,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,4,1,0,0,0,0,0,2,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BT_NO[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,9,153,156,6,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,3,0,61,255,255,183,8,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,3,0,66,252,247,255,186,10,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,2,3,0,65,255,253,255,255,188,11,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,44,221,195,23,0,6,0,65,255,251,227,255,255,191,13,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,85,255,255,208,23,0,1,65,255,254,69,167,255,255,193,16,2,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,5,159,255,255,209,25,0,69,255,255,63,0,175,252,255,165,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,160,255,255,212,17,55,255,255,57,16,197,251,255,150,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,159,255,255,198,88,254,254,88,198,255,255,171,6,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,158,255,255,244,254,254,244,255,255,167,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,158,255,255,255,255,255,255,164,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,153,251,253,253,251,156,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,18,235,254,254,236,19,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,21,204,254,253,253,253,204,20,0,3,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,3,0,20,204,255,255,255,255,255,255,203,19,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,21,205,255,255,214,254,254,214,255,255,203,19,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,21,206,255,255,151,60,255,255,60,151,255,255,203,21,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,22,206,255,255,167,0,63,255,255,66,0,169,253,255,170,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,1,21,203,255,255,163,1,1,67,255,255,54,25,206,251,255,148,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,92,255,255,162,0,4,0,65,255,253,101,211,255,255,176,6,2,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,22,169,140,0,1,5,0,65,255,252,252,255,255,178,5,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,65,255,253,251,255,180,5,1,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,1,3,3,1,0,3,0,66,252,247,255,182,6,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,60,255,255,183,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,8,151,157,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BT_CONN[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,8,153,155,6,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,3,0,61,255,255,181,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,3,0,66,252,247,255,183,8,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,1,0,2,3,0,65,255,253,255,255,185,9,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,40,219,199,25,0,6,0,65,255,251,228,255,255,187,10,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,77,255,255,210,24,0,1,65,255,254,69,170,255,255,188,13,2,1,0,0,0,0,0,0,0, +0,0,0,0,0,0,2,4,153,255,255,209,25,0,69,255,255,63,0,179,252,255,156,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,155,255,255,211,15,55,255,255,57,14,197,252,255,141,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,3,5,4,0,155,255,255,195,84,254,254,84,195,255,255,165,4,3,5,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,155,255,255,241,254,254,241,255,255,163,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,96,113,15,0,4,0,155,255,255,255,255,255,255,161,0,3,1,15,113,95,2,1,0,0,0,0,0,0,0,3,0,125,255,255,183,3,3,3,0,152,251,253,253,251,154,1,2,3,3,183,255,255,125,0,3,0,0,0,0,0,0,3,0,195,254,250,247,13,0,5,0,18,235,254,254,235,18,0,5,0,13,247,250,254,195,0,3,0,0,0,0,0,0,3,0,88,250,255,140,0,5,0,20,203,253,253,253,253,202,19,0,5,0,140,255,249,87,0,3,0,0,0, +0,0,0,0,1,0,41,54,0,2,0,19,202,255,255,255,255,255,255,200,17,0,2,0,54,41,0,1,0,0,0,0,0,0,0,0,0,1,0,0,3,0,19,203,255,255,210,254,254,210,255,255,199,16,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,2,5,0,19,202,255,255,147,58,255,255,58,148,255,255,198,18,2,4,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,19,202,255,255,165,0,64,255,255,67,0,170,253,255,161,0,4,0,0,0,0,0,0,0,0,0,0,0,0,1,2,19,198,255,255,164,1,1,67,255,255,53,28,209,251,255,140,0,3,0,0,0,0,0,0,0,0,0,0,0,0,3,0,83,255,255,165,1,4,0,65,255,253,102,213,255,255,169,5,2,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,19,166,143,1,1,5,0,65,255,252,252,255,255,172,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,65,255,253,251,255,175,3,1,2,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,1,3,3,1,0,3,0,66,252,247,255,178,5,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,60,255,255,180,6,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,8,151,156,6,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BT_DIS[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,1,8,153,155,6,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,83,165,43,0,4,0,0,0,0,3,0,61,255,255,181,7,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,224,255,231,47,0,4,0,0,0,3,0,66,252,247,255,182,7,0,2,0,0,0,0,0,0,0,0,0,0,0,2,0,112,255,255,232,45,0,4,0,0,3,0,65,255,253,254,255,183,7,0,2,0,0,0,0,0,0,0,0,0,0,0,2,0,122,255,255,231,44,0,4,0,3,0,65,255,251,240,255,255,183,7,0,1,0,0,0,0,0,0,0,0,0,0,0,4,0,124,255,255,230,43,0,4,3,0,64,255,253,82,188,255,255,184,10,2,1,0,0,0,0,0,0,0, +0,0,0,1,4,0,126,255,255,229,41,0,7,0,68,254,255,59,7,187,252,255,157,0,3,0,0,0,0,0,0,0,0,0,0,0,1,4,0,127,255,255,228,40,0,2,52,255,254,60,2,183,252,255,161,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,128,255,255,227,39,0,2,108,255,70,175,255,255,188,13,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,130,255,255,226,38,0,0,116,246,255,255,188,10,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,132,255,255,225,36,0,0,121,255,187,10,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,133,255,255,223,35,0,0,85,16,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,134,248,255,222,34,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,1,0,220,251,255,221,33,0,4,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,2,1,5,178,254,255,252,255,220,31,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,4,176,255,255,231,254,252,255,218,30,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,175,255,255,179,74,254,254,255,255,217,29,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,3,174,255,255,195,6,58,255,251,212,255,255,216,28,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,7,169,255,255,194,14,0,68,255,255,52,147,253,255,214,27,0,3,0,0,0,0,0,0,0,0,0,0,0,0,3,0,74,255,255,195,14,0,1,65,255,253,83,181,253,255,255,213,26,0,3,0,0,0,0,0,0,0,0,0,0,0,1,0,26,195,178,15,0,6,0,65,255,251,241,255,255,225,255,255,212,25,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,65,255,253,253,255,183,16,144,255,255,211,24,0,2,0,0,0,0, +0,0,0,0,0,0,0,1,1,1,1,0,3,0,66,252,247,255,183,8,0,0,149,255,255,209,23,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,61,255,255,183,8,0,3,3,0,152,255,255,201,13,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,8,151,156,7,0,2,0,1,3,0,154,255,255,31,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,106,81,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const unsigned char SB_ICON_BT_SEARCH[1024]={ +0,0,0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,67,179,74,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,191,255,251,84,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,2,0,190,252,251,252,87,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,2,0,2,0,190,255,254,255,253,91,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,140,229,104,0,4,3,0,189,255,230,244,255,254,95,0,3,0,0,0,2,4,1,0,0,0,0,0,0,0,0,1,0,215,255,255,110,0,6,0,187,255,175,67,250,255,255,98,0,2,0,0,0,0,0,0,0,0,0,0, +0,0,0,2,1,57,238,255,255,111,0,2,188,255,190,0,61,241,253,255,52,0,4,0,75,140,10,1,1,0,0,0,0,0,0,0,2,0,59,239,255,255,115,0,188,255,188,0,98,245,255,248,44,1,4,0,206,255,116,0,4,0,0,0,0,0,0,0,0,4,0,58,239,255,255,102,177,255,177,99,255,255,248,74,0,2,4,0,134,251,210,4,1,1,0,0,0,0,0,0,0,0,4,0,57,239,255,254,243,255,242,254,255,245,70,0,0,0,0,0,49,253,252,44,0,2,0,0,0,0,0,0,0,0,0,4,0,57,238,255,255,255,255,255,243,66,0,0,53,207,52,0,11,223,255,89,0,4,0,0,0,0,0,0,0,0,0,0,4,0,59,225,253,253,252,227,64,0,4,57,233,255,113,0,0,193,255,116,0,4,0,0,0,0,0,0,0,0,0,0,0,7,0,137,255,248,255,141,0,10,0,171,255,249,126,0,0,186,255,124,0,4,0,0,0,0,0,0,0,0,0,0,4,0,107,250,252,254,252,250,109,0,4,29,210,255,108,0,1,197,255,112,0,4,0,0, +0,0,0,0,0,0,0,4,0,105,255,255,255,255,255,255,255,107,0,0,29,177,43,0,15,230,255,79,0,3,0,0,0,0,0,0,0,0,4,0,106,255,255,235,225,255,224,234,255,255,108,0,0,0,0,0,59,255,244,28,0,2,0,0,0,0,0,0,0,4,0,107,255,255,243,54,174,255,174,52,242,255,255,107,0,5,4,0,151,252,187,0,2,0,0,0,0,0,0,0,2,0,107,255,255,242,65,0,190,255,191,0,49,240,253,255,54,0,4,0,198,255,83,0,3,0,0,0,0,0,0,3,0,101,255,255,241,62,0,2,187,255,186,0,113,246,254,248,44,0,4,0,42,83,0,1,0,0,0,0,0,0,0,1,0,224,255,239,61,0,7,0,187,255,181,118,255,255,251,77,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,93,179,55,0,4,2,0,190,255,246,255,253,251,79,0,3,0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,190,255,251,252,251,80,0,4,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,2,3,2,0,0,2,0,190,252,251,252,81,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,190,255,251,83,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,64,178,77,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +static const sb_icon_t sb_icons[] = { + { SB_ICON_WIFI }, + { SB_ICON_BT }, + { SB_ICON_BATT }, + { SB_ICON_VOL }, + { SB_ICON_BRIGHT }, + { SB_ICON_WIFI_STRONG }, + { SB_ICON_WIFI_MEDIUM }, + { SB_ICON_WIFI_WEAK }, + { SB_ICON_WIFI_NO }, + { SB_ICON_WIFI_OFF }, + { SB_ICON_BT_NO }, + { SB_ICON_BT_CONN }, + { SB_ICON_BT_DIS }, + { SB_ICON_BT_SEARCH }, +}; + +/* 枚举索引(与 sb_icons[] 对应) */ +enum { SB_I_WIFI=0, SB_I_BT=1, SB_I_BATT=2, SB_I_VOL=3, SB_I_BRIGHT=4, + SB_I_WIFI_STRONG=5, SB_I_WIFI_MEDIUM=6, SB_I_WIFI_WEAK=7, + SB_I_WIFI_NO=8, SB_I_WIFI_OFF=9, SB_I_BT_NO=10, SB_I_BT_CONN=11, + SB_I_BT_DIS=12, SB_I_BT_SEARCH=13 }; +#endif /* SB_ICONS_H */ \ No newline at end of file diff --git a/test_sb_statusbar.c b/test_sb_statusbar.c new file mode 100644 index 0000000..ca3af2b --- /dev/null +++ b/test_sb_statusbar.c @@ -0,0 +1,113 @@ +/* test_sb_statusbar.c — 宿主交叉验证「同一个状态栏」闭环的纯逻辑(不依赖 /dev/fb0/字库): + * 1. 数据源唯一:launcher 经 meow_ui_sys_feed 喂权威快照 → meow_ui_sys_get() 反映一致 + * (batt/vol/wifi_connected/wifi_level/bt/brightness)。 + * 2. 系统模式门:launcher 置 system_mode=1 才渲染(meow_ui_sys_statusbar_render 画图标+值); + * App 进程默认 0 → render 为 no-op(不画顶条、无双画)。 + * 3. 图标簇+值:fed 数据 → render 依次调用 meow_gfx_blit_icon(电量/音量/蓝牙/WiFi/亮度) + * 与 meow_ui_text_right(%n 值);WiFi 已连-50dBm → WIFI_STRONG。 + * + * 编译(宿主 gcc): + * gcc -o test_sb_statusbar test_sb_statusbar.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c \ + * -I. -I../meow_launcherd + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" + +#include +#include +#include +#include + +/* ---- gfx/text/icon/log 打桩(宿主无 fb/字库) ---- */ +static int g_calls_icon[16]; /* meow_gfx_blit_icon 的 idx 调用计数 */ +static int g_calls_icon_total = 0; +static char g_last_text_right[128]; /* 最近一次 meow_ui_text_right 的串 */ +static int g_fill_top = 0; /* 清顶条(0,0,w,28)调用计数 */ +static int g_render_calls = 0; /* statusbar_render 里真正执行了的次数(用 fill 计数近似) */ +static int g_collapsed = 0; + +int meow_ui_width(void) { return 480; } +int meow_ui_text_height(int size) { (void)size; return 20; } +int meow_ui_text_width(const char *s, int size) { (void)size; return (int)strlen(s) * 7; } +void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t c) { + (void)c; if (x == 0 && y == 0 && w == 480 && h == 28) g_fill_top++; +} +void meow_ui_text(int x, int y, const char *s, int size, uint32_t c) { (void)x;(void)y;(void)s;(void)size;(void)c; } +void meow_ui_text_right(int rx, int y, const char *s, int size, uint32_t c) { + (void)rx;(void)y;(void)size;(void)c; + snprintf(g_last_text_right, sizeof g_last_text_right, "%s", s); +} +void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy) { + (void)x;(void)y;(void)ink_h;(void)fg;(void)cy; + if (idx >= 0 && idx < 16) g_calls_icon[idx]++; + g_calls_icon_total++; +} +int meow_ui_statusbar_collapsed(void) { return g_collapsed; } +void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl;(void)fmt; } + +/* ---- feed 权威快照(模拟 launcher sb_sdk_refresh / app_overlay_statusbar) ---- */ +static void feed(const meow_ui_feed *f) { meow_ui_sys_feed(f); } + +int main(void) +{ + /* init + 系统模式(launcher 进程) */ + meow_ui_sys_init(); + assert(meow_ui_sys_render_enabled() == 0); /* 默认 App 模式 */ + + /* 1) 数据源唯一:喂入 → sys_get 一致 */ + meow_ui_feed f; + memset(&f, 0, sizeof f); + f.batt = 81; f.charging = 1; f.wifi_on = 1; f.wifi_connected = 1; f.wifi_level = -50; + f.bt_seen = 1; f.bt_on = 1; f.vol = 60; f.brightness = 100; + feed(&f); + const meow_sys_state *s = meow_ui_sys_get(); + assert(s->batt == 81 && s->charging == 1); + assert(s->wifi_on == 1 && s->wifi_connected == 1 && s->wifi_level == -50); + assert(s->bt_seen == 1 && s->bt_on == 1); + assert(s->vol == 60 && s->brightness == 100); + printf("PASS data-source unique: feed -> sys_get consistent (batt=%d vol=%d wifi_lvl=%d bri=%d)\n", + s->batt, s->vol, s->wifi_level, s->brightness); + + /* 2a) App 进程(system_mode=0):render 应 no-op(无图标 blit、无顶条 fill), + * 但 inset 仍为 28px(App 内容下移让位给 launcher 叠画的状态栏)——即「有 inset、 + * 无状态栏实例」,根治双画。 */ + memset(g_calls_icon, 0, sizeof g_calls_icon); g_calls_icon_total = 0; g_fill_top = 0; + meow_ui_set_statusbar(1); + assert(meow_ui_statusbar_height() == MUI_SBAR_H); /* App 仍 inset 28 */ + assert(meow_ui_statusbar_visible() == 1); + meow_ui_sys_statusbar_render(); + assert(g_calls_icon_total == 0 && g_fill_top == 0); + printf("PASS app-mode gate: render no-op (no icon/fill) + inset=28 -> no double draw\n"); + + /* 2b) 系统进程(system_mode=1):render 画图标+值 */ + meow_ui_sys_set_system_mode(1); + assert(meow_ui_sys_render_enabled() == 1); + memset(g_calls_icon, 0, sizeof g_calls_icon); g_calls_icon_total = 0; g_fill_top = 0; + g_last_text_right[0] = 0; + meow_ui_sys_statusbar_render(); + assert(g_fill_top >= 1); /* 清整条顶栏 */ + assert(g_calls_icon[MEOW_SB_ICON_BATT] >= 1); /* 电量图标 */ + assert(g_calls_icon[MEOW_SB_ICON_VOL] >= 1); /* 音量图标 */ + assert(g_calls_icon[MEOW_SB_ICON_BT_CONN] >= 1);/* 蓝牙连接图标 */ + assert(g_calls_icon[MEOW_SB_ICON_WIFI_STRONG] >= 1); /* WiFi 已连-50dBm → 强 */ + assert(g_calls_icon[MEOW_SB_ICON_BRIGHT] >= 1);/* 亮度图标 */ + assert(g_calls_icon[MEOW_SB_ICON_WIFI_STRONG] >= 1); /* 已连 → 强(正确分档) */ + assert(g_calls_icon[MEOW_SB_ICON_WIFI_WEAK] == 0); + printf("PASS icon cluster: batt/vol/bt/wifi(strong)/bright blitted, values=%s\n", + g_last_text_right); + + /* 3) WiFi 弱信号(-80dBm)→ WEAK;关网卡 → 不画 WiFi */ + memset(g_calls_icon, 0, sizeof g_calls_icon); + f.wifi_connected = 1; f.wifi_level = -80; feed(&f); + meow_ui_sys_statusbar_render(); + assert(g_calls_icon[MEOW_SB_ICON_WIFI_WEAK] >= 1); + memset(g_calls_icon, 0, sizeof g_calls_icon); + f.wifi_on = 0; f.wifi_connected = 0; f.wifi_level = -1000; feed(&f); + meow_ui_sys_statusbar_render(); + assert(g_calls_icon[MEOW_SB_ICON_WIFI_STRONG] == 0 && g_calls_icon[MEOW_SB_ICON_WIFI_WEAK] == 0); + printf("PASS wifi tiering: weak(-80)->WEAK, off->no wifi icon\n"); + + printf("ALL PASS\n"); + return 0; +} From 5ee99265d2985927ff7fb3b09a68c80752176985 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 19:54:48 +0800 Subject: [PATCH 12/49] =?UTF-8?q?fix(statusbar):=20present()=20=E5=B0=8A?= =?UTF-8?q?=E9=87=8D=20inset=20=E2=80=94=20App=E8=BF=9B=E7=A8=8B=E4=B8=8D?= =?UTF-8?q?=E5=86=99=E9=A1=B6=E6=9D=A128px=E5=8C=BA,=20=E6=A0=B9=E6=B2=BB?= =?UTF-8?q?=E8=BF=9BApp=E9=A1=B6=E6=9D=A1=E9=BB=911s(agent-145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因: App首帧 present() 全屏拷贝 meow_ui_gfx.c:739 把顶条区写成黑(g_back_buf新缓冲顶条行未初始化)→覆盖launcher叠画顶条; 下次叠加~1s后→黑1s - 修: SDK present() 真实fb拷贝加 sb_clip 截断(App进程 !meow_ui_sys_render_enabled() 不写 ins 区[0,statusbar_height)); launcher(sb_clip=0)行为不变 - hello 零改动(SDK通用覆盖所有App); launcher立即预画(:9132)+1s刷新 结果不再被冲掉 - meow_ui.a=900205ea launcher=97b73cbf hello=91da9ab0 0warning; 宿主 test_sb_statusbar/test_sb_clamp ALL PASS - ②App内下拉通知中心: 评估确认属 ④App内通知中心(M3)完整专项(launch_app不读触摸/输入移交竞态/SIGSTOP会话/不调meow_ipc_process), 未冒风险落固件, 标注M3 --- meow_ui_gfx.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index 6c7d3a5..b23c363 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -736,13 +736,37 @@ static void present(void) if (g_fb_mem) { ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */ unsigned long long tbg = now_ms(); + /* 「同一个状态栏」闭环:App 进程不渲染状态栏(meow_ui_sys_render_enabled()==0),也绝不向 + * 真实 fb 写顶条 inset 区(顶条由 launcher 系统进程叠加绘制)。否则 App 首帧全量拷贝 + * (meow_ui_init 置 dirty_full)会把 launcher 刚叠画的顶条冲成未初始化数据——真机 + * 「进入 App 顶条黑 1 秒再出现」。sb_clip = 本进程当前状态栏 inset(展开=28 / 收合=4 / + * 隐藏=0):全量拷贝只拷 [sb, yres) 行;逐 dirty 矩形把上沿截断到 y>=sb,整块落在顶条区则跳过。 + * 仅 App 进程(非系统 UI)截断;launcher(系统 UI)sb_clip=0,行为不变。 */ + int sb_clip = 0; + if (!meow_ui_sys_render_enabled()) sb_clip = meow_ui_statusbar_height(); + if (sb_clip < 0) sb_clip = 0; + if (sb_clip > g_yres) sb_clip = g_yres; if (g_dirty_full) { - memcpy(g_fb_mem, g_back_buf, (size_t)g_line * (size_t)g_yres); + if (sb_clip > 0) { + memcpy(g_fb_mem + (size_t)sb_clip * g_line, + g_back_buf + (size_t)sb_clip * g_line, + (size_t)g_line * (size_t)(g_yres - sb_clip)); + } else { + memcpy(g_fb_mem, g_back_buf, (size_t)g_line * (size_t)g_yres); + } } else { int bppb = g_bpp / 8; for (int i = 0; i < g_ndirty; i++) { - int rw = g_dirty[i][2], ry = g_dirty[i][1]; - for (int yy = 0; yy < g_dirty[i][3]; yy++) { + int rw = g_dirty[i][2], ry = g_dirty[i][1], rh = g_dirty[i][3]; + if (sb_clip > 0) { + if (ry + rh <= sb_clip) continue; /* 整块在顶条 inset 区:App 不写 */ + if (ry < sb_clip) { /* 上沿与顶条区相交:截断到 sb_clip */ + rh -= (sb_clip - ry); + ry = sb_clip; + if (rh <= 0 || rw <= 0) continue; + } + } + for (int yy = 0; yy < rh; yy++) { size_t off = (size_t)(ry + yy) * g_line + (size_t)g_dirty[i][0] * bppb; memcpy(g_fb_mem + off, g_back_buf + off, (size_t)rw * bppb); } From 1880aba139fe809faf35aabfb92c74eba5ae493e Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 20:18:13 +0800 Subject: [PATCH 13/49] =?UTF-8?q?fix(statusbar):=20=E5=85=A8=E5=B1=8F?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=A0=8F=E6=94=B6=E5=90=88=20=E2=80=94=20?= =?UTF-8?q?=E4=B8=8A=E7=A7=BB=E5=87=BA=E5=B1=8F+=E5=81=9C=E6=B8=B2?= =?UTF-8?q?=E6=9F=93+=E7=83=AD=E5=8C=BA28=E4=BF=9D=E7=95=99+=E5=B7=A6?= =?UTF-8?q?=E7=BC=98=E6=8C=87=E7=A4=BA=E6=9D=A1=E5=B1=8F=E9=A1=B6(agent-14?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因: launcher/App 双进程各持SDK态, hello运行时全屏(set_fullscreen+set_collapsed)只改App进程, launcher仍expanded→画28px+时序闪 - SDK render_off(yoff): 顶条内容整体上移+裁顶, 收合 statusbar_collapsed_render 只画4px提示线(无时钟/图标/值) - 热区保留: meow_ui_statusbar_hotzone() 收合时返回28(渲染与热区事件分离)→收合仍可下滑 - 左缘指示条: meow_ui_swipe_hint y=statusbar_height()=4 平移屏顶 - 模式感知: /tmp/meowapp_sbar(App进程SDK写0展开/1收合/2隐藏, launcher读); launch_app循环新变立即叠(消半秒闪)+过渡期~20ms高频刷(动画平滑) - meow_ui.a=ed0267a9 launcher=7ea80408 hello=1a3ff13e 0warning; 宿主 test_sb_collapse/test_sb_statusbar ALL PASS - 遗留: 全屏上收观感/收合下App前台打开通知中心归④M3(meow_ui_sys_notif_pull桩) --- meow_ui.h | 12 +++++++-- meow_ui_gfx.c | 26 ++++++++++++++++---- meow_ui_internal.h | 9 +++++++ meow_ui_sys.c | 61 ++++++++++++++++++++++++++++++++++++---------- meow_ui_sys.h | 9 +++++++ 5 files changed, 97 insertions(+), 20 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index bcb94b7..9b89ba3 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -223,10 +223,18 @@ int meow_ui_statusbar_height(void); 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);占位,后续代理实现插值 */ +/* 当前上收进度(0..MUI_SBAR_H:0=完全展开,28=内容全部上移出屏、仅留提示线)。 + * 供系统进程(launcher)在 App 前台叠加顶条时读取,按「当前可见条高 = 28-offset」 + * 标脏/切齐,实现收合时只 blit 可见条高、其余行保留 App 内容(平滑上收、无残留)。 */ +int meow_ui_statusbar_collapse_offset(void); +/* 上收/恢复过渡是否仍在进行(current offset != target,逐帧推进中)。 + * 供 launcher 在过渡期高频刷新顶条(launch_app 循环据此缩短 sleep),驱动平滑动画;稳定后回 1s 慢刷。 */ +int meow_ui_statusbar_animating(void); /* 顶部下滑热区(下拉展开状态栏/通知)高度(px)。 - * 非全屏=整条状态栏可视高度;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 - * 返回「可用于起始下拉」的热区高度;由输入层/后续代理据此判定下拉起点。 */ + * 状态栏「激活」(含**收合**)时恒=整条状态栏高 MUI_SBAR_H(28)——收合只上收渲染、**不禁用顶部热区** + * (渲染与热区事件分离,全屏下顶部下滑仍开通知中心);仅「全屏隐藏」(immersive inset0)时缩小到 + * MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。返回「可用于起始下拉」的热区高度。 */ int meow_ui_statusbar_hotzone(void); #define MEOW_SBAR_HOTZONE_FS 4 /* 全屏时缩小后的顶部热区高度(px,草案值) */ diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index b23c363..4eb9c3c 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -177,6 +177,7 @@ static void dirty_add(int x, int y, int w, int h) void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } void meow_gfx_mark_full(void) { dirty_full(); } +void meow_gfx_dirty_reset(void) { dirty_reset(); } /* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它, * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ @@ -674,10 +675,21 @@ void meow_ui_statusbar_set_collapsed(int on) g_sbar_collapse_from_h = g_sbar_collapse_h; g_sbar_collapse_start_ms = now_ms(); dirty_full(); /* 顶栏显隐/上收变化 → 全帧重画 */ + meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合,agent-146) */ } int meow_ui_statusbar_collapsed(void) { return g_sbar_collapsed; } +/* 当前上收进度(0..MUI_SBAR_H)。供 launcher 按「可见条高 = 28-offset」标脏切齐(agent-146)。 */ +int meow_ui_statusbar_collapse_offset(void) { return g_sbar_collapse_h; } + +/* 上收/恢复过渡是否仍在推进(current offset != target)。供 launcher 过渡期高频刷(agent-146)。 */ +int meow_ui_statusbar_animating(void) +{ + int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0; + return g_sbar_collapse_h != tgt; +} + void meow_ui_statusbar_animate(int ms) { g_sbar_collapse_anim_ms = (ms > 0) ? ms : 0; /* 0=瞬切(基座默认) */ @@ -685,11 +697,12 @@ void meow_ui_statusbar_animate(int ms) int meow_ui_statusbar_hotzone(void) { - /* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 - * 上收(collapsed、非全屏)时热区=提示线下缘高度(MUI_SBAR_COLLAPSE_H,与渲染/inset 同口径)。 - * 全屏态由 meow_ui_sys 维护。 */ + /* 通知中心触发(顶部下滑)热区:与渲染分离(agent-146 定案:收合 ≠ 禁用顶部热区)。 + * 状态栏「激活」(非全屏隐藏,含收合)时热区恒 = 整条状态栏高 MUI_SBAR_H(28)——即使收合 + * 也只上收渲染,顶部下滑仍可打开通知中心(渲染与热区事件分离)。 + * 仅「全屏隐藏」(immersive,inset0)时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 */ if (meow_ui_sys_statusbar_active()) - return g_sbar_collapsed ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; + return MUI_SBAR_H; return MEOW_SBAR_HOTZONE_FS; } @@ -731,7 +744,10 @@ static void present(void) if (g_sbar_collapse_h >= MUI_SBAR_H) statusbar_collapsed_render(g_sbar_collapse_h); else - meow_ui_sys_statusbar_render(); + /* 收合过渡/展开态:内容上移 g_sbar_collapse_h 像素并裁顶(yoff=0=整条)。 + * 仅画可见条高 [0, 28-offset) 行,其余行=App 内容(launcher 按 statusbar_height 标脏 + * 切齐),实现「平滑上移出屏」而不残留、不黑框。 */ + meow_ui_sys_statusbar_render_off(g_sbar_collapse_h); } if (g_fb_mem) { ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */ diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 67cb73b..361e769 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -40,6 +40,10 @@ int meow_ui_sys_render_enabled(void); /* 当前进程是否渲染系统顶 /* 把全局状态栏画进本进程 back_buf 顶部 MUI_SBAR_H 行(仅系统进程调用,见 render_enabled)。 * launcher 的 App 前台叠加(launch_app 循环)直接调它 + 标脏顶条上屏。实现见 meow_ui_sys.c。 */ void meow_ui_sys_statusbar_render(void); +/* 收合「上移出屏」过渡渲染:把顶条内容整体上移 yoff 像素并裁顶(y<0 处自然剪裁,meow_font/图标 + * blit 已带边界钳制),仅画可见条高 [0, MUI_SBAR_H-yoff) 行(其上为 App 内容、由 launcher 保留)。 + * yoff=0 等价 meow_ui_sys_statusbar_render()。实现见 meow_ui_sys.c。 */ +void meow_ui_sys_statusbar_render_off(int yoff); /* ---- 状态栏 MD3 图标簇(meow_ui_sys.c 渲染右侧;位图在 SDK 侧 sb_icons.h)---- * meow_ui_gfx.c 的 meow_gfx_blit_icon(idx,...) 用这些索引查 sb_icons[]。 @@ -97,6 +101,11 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h); /* 标记整帧脏(launcher 手动脏模型桥接:自绘 raw 像素后标全屏,present 走 SDK)。 */ void meow_gfx_mark_full(void); +/* 清空脏区(含整帧标记)。用于 App 前台叠加路径:meow_ui_statusbar_set_collapsed 会标整帧脏, + * 但 launcher 在 App 上只应 blit 顶条(可见条高),绝不能整帧 blit(会把自身 back_buf 的 stale + * 菜单内容盖到 App 内容上)——叠加前用本函数撤销整帧脏,再按需标局部脏(agent-146)。 */ +void meow_gfx_dirty_reset(void); + /* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */ unsigned char *meow_ui_back_buf(void); diff --git a/meow_ui_sys.c b/meow_ui_sys.c index ea70d87..aea79f7 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -25,6 +25,11 @@ #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" +/* App 前台顶条模式文件(App 进程 SDK 写,launcher 读)。与 /tmp/meowapp_state(immersive,launcher 写/ + * watchdog 读)语义不同,独立文件避免冲突。值:'0'=展开 28px 整条;'1'=收合(提示线);'2'=隐藏(沉浸 inset0)。 + * 仅 App 进程写(!g_sys_render);launcher(系统 UI)不写,只读。App 退出时由 launcher unlink。 */ +#define MEOW_SBAR_MODE_FILE "/tmp/meowapp_sbar" + /* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */ #define BT_REFRESH_MS 30000 @@ -190,6 +195,21 @@ void meow_ui_sys_set_system_mode(int on) g_statusbar_force = 1; /* 切换即强制下次 present 反映(画/不画顶条) */ } +/* ---- App 前台顶条模式上报(agent-146 全屏收合接入)---- + * 仅在 App 进程(!g_sys_render)写;launcher(system_mode=1)不写,避免覆盖 App 的状态。 + * 由 meow_ui_set_fullscreen / meow_ui_statusbar_set_collapsed 调用,使 launcher(read 进程) + * 能感知 App 运行时的全屏/收合态,从而把新顶条切到收合(提示线)而非仍画 28px 整条。 */ +void meow_ui_sbar_write_mode(void) +{ + if (g_sys_render) return; /* 系统进程(launcher)不写 */ + char m = meow_ui_statusbar_collapsed() ? '1' + : (g_fullscreen ? '2' : '0'); + int fd = open(MEOW_SBAR_MODE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return; + if (write(fd, &m, 1) < 0) { /* ignore write error */ } + close(fd); +} + int meow_ui_sys_render_enabled(void) { return g_sys_render; @@ -200,6 +220,7 @@ void meow_ui_set_statusbar(int visible) { g_statusbar_on = visible ? 1 : 0; g_statusbar_force = 1; /* 显隐切换也要重画顶条 */ + meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */ } int meow_ui_statusbar_visible(void) @@ -211,6 +232,7 @@ void meow_ui_set_fullscreen(int on) { g_fullscreen = on ? 1 : 0; g_statusbar_force = 1; /* 进出全屏立即反映(显示/隐藏顶条) */ + meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */ } int meow_ui_statusbar_height(void) @@ -259,14 +281,14 @@ int meow_ui_sys_statusbar_force(void) /* 画一个右对齐项:右边界=*xr,画完向左推进 *xr(含 MEOW_SB_XGAP 项间隙)。 * icon>=0 画图标(可配右侧数值 txt;txt==NULL 仅图标)。文字/图标同用主题色 col。 * ink_h 传给 meow_gfx_blit_icon(主图标用 MEOW_SB_ICON_INK 等大;WiFi 强弱档传 0 保持自然 ink)。 - * 返回该项左边缘(供亮度可选判宽)。 */ -static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty) + * ycy = 图标 ink 垂直中心线(收合上移时随 -off 上移);ty = 文字 y。返回该项左边缘(供亮度可选判宽)。 */ +static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty, int ycy) { int iconw = (icon >= 0) ? MEOW_SB_ICON_SIZE : 0; int gap = (icon >= 0 && txt) ? MEOW_SB_TGAP : 0; int tw = txt ? meow_ui_text_width(txt, MUI_SBAR_FONT) : 0; int x = *xr - (iconw + gap + tw); - if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, MEOW_SB_ICON_CY); x += iconw + gap; } + if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, ycy); x += iconw + gap; } if (txt) meow_ui_text_right(*xr, ty, txt, MUI_SBAR_FONT, col); *xr -= (iconw + gap + tw) + MEOW_SB_XGAP; return *xr + MEOW_SB_XGAP; @@ -285,19 +307,31 @@ static int sb_wifi_icon(void) } void meow_ui_sys_statusbar_render(void) +{ + meow_ui_sys_statusbar_render_off(0); +} + +void meow_ui_sys_statusbar_render_off(int yoff) { if (!g_sys_render) return; /* 仅系统进程(launcher)渲染;App 不画顶条 */ if (!meow_ui_sys_statusbar_active()) return; + if (yoff < 0) yoff = 0; + if (yoff > MUI_SBAR_H) yoff = MUI_SBAR_H; const meow_theme *th = meow_theme_get(); int w = meow_ui_width(); int h = MUI_SBAR_H; + int vis = h - yoff; /* 收合上移:可见条高 = 28-yoff(其余行=App 内容,由 launcher 保留) */ + if (vis <= 0) return; /* 完全收合由 statusbar_collapsed_render(提示线)处理 */ - /* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程(launcher)渲染本函数。 */ - meow_ui_fill_rect(0, 0, w, h, th->bg); + /* 整条清底(仅可见条高 [0,vis) 行):覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程渲染本函数。 */ + meow_ui_fill_rect(0, 0, w, vis, th->bg); - /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */ + /* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 + * 收合上移:内容整体上移 yoff 像素并裁顶(meow_font/图标 blit 已带 y<0 边界钳制,安全)。 */ int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2; if (ty < 1) ty = 1; + ty -= yoff; + int ycy = MEOW_SB_ICON_CY - yoff; /* 图标 ink 垂直中心随上移 */ /* 时钟(左):每秒现取 localtime */ char timebuf[16]; @@ -314,28 +348,29 @@ void meow_ui_sys_statusbar_render(void) char val[16]; if (g_sys.batt >= 0) { /* 电量:图标+%(未知不画)——最右项 */ snprintf(val, sizeof val, "%d%%", g_sys.batt); - sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty); + sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty, ycy); } snprintf(val, sizeof val, "%d%%", g_sys.vol); - sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty); /* 音量:图标+% */ + sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty, ycy); /* 音量:图标+% */ if (g_sys.bt_seen && g_sys.bt_on) { /* 蓝牙:仅图标(上电即显示,连接态占位;多态留后续) */ - sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty); + sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty, ycy); } if (g_sys.wifi_on) { /* WiFi:仅图标(关不画;开未连/分档) */ int wco = sb_wifi_icon(); int is_weak = (wco == MEOW_SB_ICON_WIFI_MEDIUM || wco == MEOW_SB_ICON_WIFI_WEAK); - sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty); + sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty, ycy); } if (g_sys.brightness > 0) { /* 亮度:图标+%(>0 才显示;与时钟不重叠才画) */ int bl = (g_sys.brightness > 100) ? 100 : g_sys.brightness; snprintf(val, sizeof val, "%d%%", bl); int bw = MEOW_SB_ICON_SIZE + MEOW_SB_TGAP + meow_ui_text_width(val, MUI_SBAR_FONT) + MEOW_SB_XGAP; if (xr - bw > 8 + meow_ui_text_width(timebuf, MUI_SBAR_FONT) + MEOW_SB_CLK_MARGIN) - sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty); + sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty, ycy); } - /* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */ - meow_ui_fill_rect(0, h - 1, w, 1, th->dim); + /* 分隔线(栏内最底 1px,主题次要色);上移后仍在可见条高内;内容 inset=statusbar_height() 从该线之下即干净 */ + int dy = h - 1 - yoff; + if (dy >= 0 && dy < vis) meow_ui_fill_rect(0, dy, w, 1, th->dim); g_statusbar_force = 0; } diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 3a58d6b..4acfaa3 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -76,4 +76,13 @@ int meow_ui_sys_statusbar_force(void); /* 把状态栏画进 back_buf(顶部 MUI_SBAR_H 行)+ 标脏。调用方负责随后上屏。 */ void meow_ui_sys_statusbar_render(void); +/* 收合「上移出屏」过渡渲染(仅系统进程/launcher,见 meow_ui_sys_statusbar_render):内容整体上移 + * yoff 像素并裁顶;只画可见条高 [0, MUI_SBAR_H-yoff) 行,其余行保留(App 内容由 launcher 标脏切齐)。 + * yoff=0 等价 meow_ui_sys_statusbar_render()。实现见 meow_ui_sys.c。 */ +void meow_ui_sys_statusbar_render_off(int yoff); + +/* 把当前顶条模式写到 /tmp/meowapp_sbar(仅 App 进程 !g_sys_render 才写;launcher 读)。 + * meow_ui_statusbar_set_collapsed(gfx.c)也调用它,故在头文件声明。 */ +void meow_ui_sbar_write_mode(void); + #endif /* MEOW_UI_SYS_H */ From 3d6de434109f6a5a69fb31cbbd240d0ace6561e2 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 21:38:30 +0800 Subject: [PATCH 14/49] =?UTF-8?q?refactor(launcher):=20=E6=8B=86=E5=88=86?= =?UTF-8?q?=20meow=5Flauncherd.c(9778=E8=A1=8C)=E2=86=92meow=5Flauncherd/?= =?UTF-8?q?=20=E5=A4=9A=E6=96=87=E4=BB=B6(agent-149)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 14 模块 .c + meow_launcher_internal.h(642): util/font_theme/fb_compositor/settings_page/power_menu/notif_center/app_launcher/main_menu/audio/backlight/net/keys/about/statusbar; meow_launcherd.c 已删 - 按行逐字搬移(0-diff), 118函数提升非static+47组extern(内部头), #if0退役区不编入(源码留.pre_split.bak) - 关于本机小改: 作者 AskaEth/17 + info/item[13]防越界 + s_dd_modal 超高滚动(clamp/触摸滚动/音量键跟随/裁剪, 仅超高启用) - build_launcher.sh 编译列表→模块; 文档/注释引用 meow_launcherd.c→模块/目录 - 编译0warning/-Wall -Wextra告警集与原文一致(77条无新增)/qemu-mipsel冒烟一致(--scan/--sysdump); launcher=c0799357(505KB; md5因多文件重编译与原文异,非回归依据) - 备份 meow_launcherd.c.pre_split.bak/meow_launcherd.pre_split.bak; M2.69(0.7.64)已推卡并用户验证OK --- meow_ui_gfx.c | 6 +++--- meow_ui_internal.h | 2 +- meow_ui_vol.c | 2 +- meow_ui_vol.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index 4eb9c3c..fc359cc 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -35,7 +35,7 @@ #define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" /* 默认字库(卡 MeowOS/fonts/default/regular,M2.6 字重结构)与固件内置之外,launcher 会把 - * 「当前生效字体」经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): + * 「当前生效字体」经环境变量下发给 app(见 meow_launcherd/app_launcher.c `launch_app` / font_theme.c `meow_font_effective_paths`): * MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = 或 "default"。 * SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */ @@ -879,8 +879,8 @@ static int load_font(void) } /* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、 - * ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c - * launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类 + * ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd/app_launcher.c + * `launch_app`):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类 * MEOW_FONT_CLS__PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。 * 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */ static void meow_gfx_load_multifont(void) diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 361e769..a38a059 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -14,7 +14,7 @@ * (launcher 是背光/音量写入方)。故此 API 让 launcher 把权威快照喂进 SDK 的系统状态, * 喂入即置状态栏 force,下一次 present() 重画顶条。SDK 自身 meow_ui_sys_refresh() 仍按 * 同源兜底(供 App 流程 meow_ui_run 使用)。只填 SDK 快照所需最小字段,与 launcher 的 - * meow_sys_state(meow_launcherd.c)字段名交叉映射,二者是不同 TU 的异名类型,故用此 + * meow_sys_state(util.c)字段名交叉映射,二者是不同 TU 的异名类型,故用此 * 专用喂入结构,避免 launcher 引入 meow_ui_sys.h 造成 typedef 重定义。 */ typedef struct meow_ui_feed { int batt; /* 电量 %,-1 未知 */ diff --git a/meow_ui_vol.c b/meow_ui_vol.c index b428653..2f3cb4d 100644 --- a/meow_ui_vol.c +++ b/meow_ui_vol.c @@ -5,7 +5,7 @@ * 1. 纯函数(clamp / 键读取)不依赖 meow_ui 其它子系统,宿主 gcc 直接可测 * (test_meow_ui_vol.c),无需链整库/stub。 * 2. 把「音量从 settings 读数并 clamp 到 volume_max」这一语义钉死在单点, - * 与 launcher 的 vol_clamp(meow_launcherd.c)口径对齐,杜绝两端音量漂移。 + * 与 launcher 的 vol_clamp(audio.c)口径对齐,杜绝两端音量漂移。 * * 数据源:/usr/data/meow/settings.conf 扁平 key=value。launcher meow_settings.c * 落盘为 "key = value"(带空格),本解析兼容无空格/多空格,与 launcher 读循环 diff --git a/meow_ui_vol.h b/meow_ui_vol.h index 9a94bed..fa28c42 100644 --- a/meow_ui_vol.h +++ b/meow_ui_vol.h @@ -15,7 +15,7 @@ #define MEOW_UI_VOL_MAX_DEF 100 /* 纯函数:把软件音量夹到 [0, MEOW_UI_VOL_MAX],再夹到 volume_max 软上限。 - * 语义与 launcher 的 vol_clamp(meow_launcherd.c)一致: + * 语义与 launcher 的 vol_clamp(audio.c)一致: * - 硬上限 100(0-100 口径); * - 尊重 audio.volume_max(用户把上限调小则音量被夹到该值); * - volume_max 异常(<0)兜底为 0(不进负值)。 From 26074e47ac1ab7fbebaec628bad98b1397674085 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 21:57:46 +0800 Subject: [PATCH 15/49] =?UTF-8?q?feat(sdk):=20=E6=8E=A7=E4=BB=B6=E5=BA=93?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=89=B9=E9=80=9A=E7=94=A8=E5=9F=BA=E5=BA=A7?= =?UTF-8?q?(agent-150)=20+=20launcher/hello=20=E9=87=8D=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - meow_widget 基座收敛(尾部追加 Options+状态机位+自定义回调+布局子项), W_CUSTOM/W_LAYOUT/W_SPACER - meow_theme 尾部追加 MD3 语义色+风格参数(旧字段 bg/fg/accent/dim 映射兼容), g_theme[2][2](style×mode×明暗), display.background 背景源深浅成对, 图片路径回退 - meow_widget_opts(Modifier式, 不改create签名)/MEOW_WS_*状态机(enabled阻断hit,pressed)/meow_widget_custom_create(render/hit_cb)/meow_row/column/box+arrange(算绝对坐标兼容) - meow_ui_set_style/style/meow_theme_get 完整主题(MD3默认/MIUIx占位) - meow_ui.a=29c5078d hello=24116e6b(向后兼容外观零变化) launcher重链=626546f9;build_sdk 0warning; 宿主 test_sdk_controls T1-T10 PASS + test_sb_statusbar ALL PASS - 未做(第二批/后续): Material子集/实时按压刷新扩展/物理键焦点移动运行时(留草案) --- meow_ui.h | 116 ++++++++++++++++- meow_ui_theme.c | 135 ++++++++++++++++++-- meow_ui_widget.c | 325 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 527 insertions(+), 49 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index 9b89ba3..95b4821 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -264,13 +264,37 @@ unsigned long long meow_ui_frames_presented(void); /* ============================================================ */ typedef enum { MEOW_THEME_LIGHT, MEOW_THEME_DARK, MEOW_THEME_AUTO } meow_theme_mode; +/* 主题(§5.8.4 + §8.1.3)。既有字段 bg/fg/accent/dim 保留(兼容映射,见下表), + * 其后追加 MD3 语义色板 + 风格参数段(扩展兼容——既有代码读旧字段不受影响)。 + * 映射:bg↔surface、fg↔onSurface、accent↔primary、dim↔onSurfaceVariant。 + * 「当前 style×mode」完整主题 = meow_theme_get()(MD3 默认;MIUIx 用 MD3 基础/占位)。 */ typedef struct meow_theme { - uint32_t bg; /* 背景色 */ - uint32_t fg; /* 前景/正文 */ - uint32_t accent; /* 强调色(按钮/选中) */ - uint32_t dim; /* 次要文字 */ + /* —— 既有字段(兼容保留;映射见上)—— */ + uint32_t bg; /* 背景色 = surface */ + uint32_t fg; /* 前景/正文 = onSurface */ + uint32_t accent; /* 强调色(按钮/选中)= primary */ + uint32_t dim; /* 次要文字 = onSurfaceVariant */ int font_title; /* 28 */ int font_body; /* 20 */ + + /* —— MD3 语义色板(§5.8.4;日/夜各一组,AUTO 按 meow_theme_mode 切换)—— */ + uint32_t primary, onPrimary, primaryContainer, onPrimaryContainer; + uint32_t secondary, onSecondary, secondaryContainer, onSecondaryContainer; + uint32_t tertiary, onTertiary, tertiaryContainer, onTertiaryContainer; + uint32_t surface, onSurface, surfaceVariant, onSurfaceVariant; + uint32_t surfaceContainerLowest, surfaceContainerLow, surfaceContainer, + surfaceContainerHigh, surfaceContainerHighest; + uint32_t surfaceDim, surfaceBright, surfaceTint; + uint32_t outline, outlineVariant, scrim; + uint32_t error, onError, errorContainer, onErrorContainer; + uint32_t inverseSurface, inverseOnSurface, inversePrimary; + uint32_t shadow; /* 仅状态层/描边近似用;无 GPU 不做投影(§5.8.7) */ + + /* —— 风格参数段(§8.1.3;MD3 默认 / MIUIx 加大圆角+描边+间距)—— */ + int radius; /* 控件默认圆角(按钮等,px) */ + int radius_card; /* 卡片/容器默认圆角(px) */ + int spacing; /* 布局/间距步进(px) */ + int stroke_w; /* 默认细描边宽(0=无边框,MD3;MIUIx=1) */ } meow_theme; const meow_theme *meow_theme_get(void); @@ -295,6 +319,90 @@ meow_theme meow_theme_defaults(meow_theme_mode m); void meow_ui_set_style(int style); /* 设顶级主题风格族;越界 clamp 到合法值;同值忽略 */ int meow_ui_style(void); /* 当前 style(默认 MEOW_STYLE_MD3) */ +/* ============================================================ */ +/* §5.8 控件层升级(第一批=通用基座):Options / 状态机 / 自定义画布 / 布局 */ +/* ============================================================ */ + +/* ---- §5.8.2 Modifier 式通用 Options(轻量结构体,非链式 DSL)---- + * 经 meow_widget_set_opts 一次性挂载,作用于任意控件(标准/自定义)。全部控件共用。 + * 哨兵语义:bg/fg==0=用控件/主题默认;stroke==0=无描边;stroke_w=描边宽; + * radius==-1=控件默认(如按钮 10),>=0=指定圆角(0=直角);pad/margin=0=无; + * disabled/selected=1=置位(立即反映到状态机位)。 */ +typedef struct meow_widget_opts { + uint32_t bg; /* 背景色(0=控件/主题默认) */ + uint32_t fg; /* 前景/文字(0=主题 onSurface) */ + uint32_t stroke; /* 描边色(0=无描边) */ + int stroke_w; /* 描边宽(px) */ + int radius; /* 圆角(px;-1=控件默认,如按钮 10) */ + int pad_x; /* 内边距(水平,px) */ + int pad_y; /* 内边距(垂直,px) */ + int margin_x; /* 外边距(水平,px;布局用) */ + int margin_y; /* 外边距(垂直,px;布局用) */ + int disabled; /* 1=禁用(视觉变暗 + 不响应命中,见状态机 §5.8.3) */ + int selected; /* 1=选中(列表高亮/勾选,见状态机 §5.8.3) */ +} meow_widget_opts; + +/* 挂载:作用于任意控件(标准/自定义)。w 内部浅拷这份 opts。 + * 返回 0=成功;NULL/无效=负值(不销毁 w)。 */ +int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts); +/* 取回当前 opts(浅拷到 out;0=成功)。自定义画布用它读通用修饰。 */ +int meow_widget_opts_get(const meow_widget *w, meow_widget_opts *out); + +/* ---- §5.8.3 统一状态机(enabled / selected / focused / pressed)---- + * 标准/自定义控件共用;visual 层由 SDK/App 按状态位叠。enabled=0 阻断 hit/pressed。 */ +#define MEOW_WS_ENABLED (1u<<0) +#define MEOW_WS_SELECTED (1u<<1) +#define MEOW_WS_FOCUSED (1u<<2) +#define MEOW_WS_PRESSED (1u<<3) + +/* 设/查状态(置位/清位,其它位不动)。on=1 置位 / 0 清位。 */ +void meow_widget_set_state(meow_widget *w, unsigned bits, int on); +unsigned meow_widget_state(const meow_widget *w); + +/* 便捷:禁用/启用、选中/取消(单态等效于 set_state)。 */ +int meow_widget_enabled(const meow_widget *w); +void meow_widget_set_enabled(meow_widget *w, int on); +int meow_widget_selected(const meow_widget *w); +void meow_widget_set_selected(meow_widget *w, int on); + +/* 一键叠状态层(自定义画布可选):把 pressed/focused/disabled/selected 的视觉层 + * 叠到 w 的矩形上(半透明状态层 + 焦点描边)。标准控件由 SDK 自动叠,无需调用; + * 自定义画布在 render_cb 里画完内容后可调它(App 也可据 meow_widget_state 自行叠)。 */ +void meow_widget_draw_state_layer(meow_widget *w, const meow_theme *th); + +/* ---- §5.8.1 自定义画布(App 提供 render/hit 回调;同一个 meow_widget)---- + * render:SDK 在 meow_widget_draw 时调用,th=当前生效主题(MD3 语义色板); + * w 的 x/y/w/h 已就位;App 用 §3/§4 绘制 API 自绘(往 back_buf 画)。 + * hit :命中判定,App 自定义(如圆形/区域外镂空);返回 1=命中。 */ +typedef void (*meow_widget_render_fn)(meow_widget *w, const meow_theme *th, void *ctx); +typedef int (*meow_widget_hit_fn)(meow_widget *w, int x, int y, void *ctx); + +/* 创建:自定义画布(x,y,w,h = 绝对坐标,同既有控件)。 + * 生命周期/Activity 归属:创建时绑定当前 active Activity(同 meow_btn_create,§6.1 ①); + * 命中走 hit_cb(先过 owner==active + enabled 双闸门)。 */ +meow_widget *meow_widget_custom_create(int x, int y, int w, int h, + meow_widget_render_fn render_cb, + meow_widget_hit_fn hit_cb, + void *ctx); + +/* ---- §5.8.5 可组合布局(Row / Column / Box + Spacer)---- + * 布局容器 = meow_widget(W_LAYOUT);arrange 把子控件算成绝对坐标(写进各 child 的 + * x/y/w/h)→ 与现有绝对定位完全兼容(draw/hit/flush_region/owner 闸门照常)。 + * 本批先支持固定尺寸子项(w/h 均已知);弹性(w/h==0)为后续增强。 */ +enum { MEOW_LAYOUT_ROW = 0, MEOW_LAYOUT_COLUMN = 1, MEOW_LAYOUT_BOX = 2 }; + +meow_widget *meow_row_create(int x, int y, int w, int h, int spacing); +meow_widget *meow_column_create(int x, int y, int w, int h, int spacing); +meow_widget *meow_box_create(int x, int y, int w, int h); + +/* 添加子控件(子可为普通控件/自定义/另一布局/Spacer)。返回 0=成功;无效=负值。 */ +int meow_layout_add(meow_widget *layout, meow_widget *child); +/* 计算子控件绝对坐标(写入各 child 的 x,y,w/h;嵌套布局递归);此后照常 draw/hit。 */ +void meow_layout_arrange(meow_widget *layout); + +/* 固定尺寸空白(也作撑余量;不可见,hit 恒 0)。 */ +meow_widget *meow_layout_spacer(int w, int h); + /* ============================================================ */ /* §9 应用日志(统一日志 API,M2.51 最小可用版) */ /* ============================================================ */ diff --git a/meow_ui_theme.c b/meow_ui_theme.c index 8a34936..5a0e997 100644 --- a/meow_ui_theme.c +++ b/meow_ui_theme.c @@ -14,17 +14,17 @@ #include "meow_ui_internal.h" /* meow_theme_reload() 定义(供 launcher 逼重读 settings) */ #include #include +#include #include #include #define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf" -static meow_theme g_light = { - 0xE8EAF0, 0x1A1A2A, 0x2A5A8A, 0x4A4F5A, 28, 20 -}; -static meow_theme g_dark = { - 0x141824, 0xFFFFFF, 0x4A6A9A, 0x9AA4C8, 28, 20 -}; +/* ---- 主题表(§5.8.4 + §8.1.3):[style][dark] ——— + * 0=MEOW_STYLE_MD3, 1=MEOW_STYLE_MIUIX;dark 0=light, 1=dark。 + * 旧字段 bg/fg/accent/dim = surface/onSurface/primary/onSurfaceVariant(映射见 meow_ui.h)。 + * bg/surface 随后由「背景全局源」(display.background,§8.1.4)按 style×mode 覆写。 */ +static meow_theme g_theme[2][2]; static meow_theme_mode g_mode = MEOW_THEME_DARK; /* 配置生效的判定模式(默认深色,与 launcher 默认一致) */ static meow_theme_mode g_manual = MEOW_THEME_DARK; /* display.theme 解析出的手动深浅;auto 未配置时段时回退用之 */ @@ -32,6 +32,7 @@ static int g_dark_start = 0; /* 深色时段开始(分 static int g_dark_end = 0; /* 深色时段结束(分钟 0-1439) */ static int g_loaded = 0; static int g_style = MEOW_STYLE_MD3; /* 顶级主题风格族(§8.1.2):默认 MD3 */ +static char g_background[64] = ""; /* display.background(black/blue/green/white/… 或图片路径) */ static meow_theme_listener g_listeners[8]; static int g_nlisteners = 0; @@ -85,6 +86,95 @@ static meow_theme_mode parse_manual(const char *v) return MEOW_THEME_DARK; /* "dark" / 未知 → 深色 */ } +/* ---- 填充 4 张主题表的内建色板(草案值,MD3 默认;MIUIx 用 MD3 基础做占位)---- + * bg/surface 本函数填默认面;随后 apply_background() 按 display.background 覆写。 */ +static void init_defaults(void) +{ + meow_theme *t; + + /* MD3 light */ + t = &g_theme[MEOW_STYLE_MD3][0]; + t->bg = 0xE8EAF0; t->fg = 0x1A1A2A; t->accent = 0x2A5A8A; t->dim = 0x4A4F5A; + t->font_title = 28; t->font_body = 20; + t->primary = 0x2A5A8A; t->onPrimary = 0xFFFFFF; t->primaryContainer = 0xC9E2F5; t->onPrimaryContainer = 0x0A2B46; + t->secondary = 0x53626E; t->onSecondary = 0xFFFFFF; t->secondaryContainer = 0xD6E5F2; t->onSecondaryContainer = 0x101E28; + t->tertiary = 0x635B78; t->onTertiary = 0xFFFFFF; t->tertiaryContainer = 0xE9DEFB; t->onTertiaryContainer = 0x1E1734; + t->surface = 0xE8EAF0; t->onSurface = 0x1A1A2A; t->surfaceVariant = 0xDDE1EA; t->onSurfaceVariant = 0x4A4F5A; + t->surfaceContainerLowest = 0xFFFFFF; t->surfaceContainerLow = 0xF2F3F7; t->surfaceContainer = 0xECEEF3; + t->surfaceContainerHigh = 0xE6E8EE; t->surfaceContainerHighest = 0xE1E3E9; + t->surfaceDim = 0xD8DAE0; t->surfaceBright = 0xFFFFFF; t->surfaceTint = 0x2A5A8A; + t->outline = 0x74777F; t->outlineVariant = 0xC4C6D0; t->scrim = 0x000000; + t->error = 0xBA1A1A; t->onError = 0xFFFFFF; t->errorContainer = 0xFFDAD6; t->onErrorContainer = 0x410002; + t->inverseSurface = 0x2F3036; t->inverseOnSurface = 0xF0F0F5; t->inversePrimary = 0xA6C8EE; + t->shadow = 0x000000; + t->radius = 12; t->radius_card = 16; t->spacing = 8; t->stroke_w = 0; + + /* MD3 dark */ + t = &g_theme[MEOW_STYLE_MD3][1]; + t->bg = 0x141824; t->fg = 0xFFFFFF; t->accent = 0x4A6A9A; t->dim = 0x9AA4C8; + t->font_title = 28; t->font_body = 20; + t->primary = 0x4A6A9A; t->onPrimary = 0xFFFFFF; t->primaryContainer = 0x2A3A5C; t->onPrimaryContainer = 0xD5E4F5; + t->secondary = 0xB8C6D2; t->onSecondary = 0x27323B; t->secondaryContainer = 0x3A4650; t->onSecondaryContainer = 0xD6E5F2; + t->tertiary = 0xCDC2E6; t->onTertiary = 0x372C52; t->tertiaryContainer = 0x4E4269; t->onTertiaryContainer = 0xE9DEFB; + t->surface = 0x141824; t->onSurface = 0xFFFFFF; t->surfaceVariant = 0x2A2F3C; t->onSurfaceVariant = 0x9AA4C8; + t->surfaceContainerLowest = 0x0F121C; t->surfaceContainerLow = 0x1C2030; t->surfaceContainer = 0x20253A; + t->surfaceContainerHigh = 0x262C44; t->surfaceContainerHighest = 0x2C334E; + t->surfaceDim = 0x12151E; t->surfaceBright = 0x3A3E50; t->surfaceTint = 0x4A6A9A; + t->outline = 0x8B93A3; t->outlineVariant = 0x3E4450; t->scrim = 0x000000; + t->error = 0xFFB4AB; t->onError = 0x690005; t->errorContainer = 0x93000A; t->onErrorContainer = 0xFFDAD6; + t->inverseSurface = 0xF0F0F5; t->inverseOnSurface = 0x2F3036; t->inversePrimary = 0xA6C8EE; + t->shadow = 0x000000; + t->radius = 12; t->radius_card = 16; t->spacing = 8; t->stroke_w = 0; + + /* MIUIx = MD3 基础(占位),primary 更饱和、圆角/间距更大、细描边(§8.1.3 可感知差异)。 + * 先整表复制 MD3,再覆写差异字段。 */ + g_theme[MEOW_STYLE_MIUIX][0] = g_theme[MEOW_STYLE_MD3][0]; + g_theme[MEOW_STYLE_MIUIX][0].primary = 0x2A5AC8; g_theme[MEOW_STYLE_MIUIX][0].accent = 0x2A5AC8; + g_theme[MEOW_STYLE_MIUIX][0].primaryContainer = 0xD6E1FA; g_theme[MEOW_STYLE_MIUIX][0].surfaceTint = 0x2A5AC8; + g_theme[MEOW_STYLE_MIUIX][0].radius = 18; g_theme[MEOW_STYLE_MIUIX][0].radius_card = 24; + g_theme[MEOW_STYLE_MIUIX][0].spacing = 10; g_theme[MEOW_STYLE_MIUIX][0].stroke_w = 1; + + g_theme[MEOW_STYLE_MIUIX][1] = g_theme[MEOW_STYLE_MD3][1]; + g_theme[MEOW_STYLE_MIUIX][1].primary = 0x4A7AE8; g_theme[MEOW_STYLE_MIUIX][1].accent = 0x4A7AE8; + g_theme[MEOW_STYLE_MIUIX][1].primaryContainer = 0x2A3A6C; g_theme[MEOW_STYLE_MIUIX][1].surfaceTint = 0x4A7AE8; + g_theme[MEOW_STYLE_MIUIX][1].radius = 18; g_theme[MEOW_STYLE_MIUIX][1].radius_card = 24; + g_theme[MEOW_STYLE_MIUIX][1].spacing = 10; g_theme[MEOW_STYLE_MIUIX][1].stroke_w = 1; +} + +/* ---- 背景全局源(§8.1.4):display.background 是「style×mode 下背景底选项集」的当前值, + * 决定主题 surface/bg(深浅成对)。按名映射 (dark_surface, light_surface): + * dark 默认/black → 0x141824 / light 默认/white → 0xE8EAF0(既有默认,保证 App 无回归); + * dark blue → 0x0A1428 / light blue → 0xE5EEFB;green → 0x0A2814 / 0xE6F6E8。 + * 图片路径(*.png/jpg/jpeg):背景图即观感 → 用默认 surface(本批草案)。 */ +static int bg_is_image(const char *v) +{ + while (*v == ' ' || *v == '\t') v++; + const char *dot = strrchr(v, '.'); + if (!dot || dot == v) return 0; + return !strncasecmp(dot, ".png", 4) || !strncasecmp(dot, ".jpg", 4) || !strncasecmp(dot, ".jpeg", 5); +} +static void bg_surfaces(const char *v, uint32_t *dark, uint32_t *light) +{ + *dark = 0x141824; *light = 0xE8EAF0; /* 默认(未知/图片/空)→ 既有默认面 */ + while (*v == ' ' || *v == '\t') v++; + if (bg_is_image(v)) return; /* 背景图:用默认 */ + if (!strncmp(v, "blue", 4)) { *dark = 0x0A1428; *light = 0xE5EEFB; return; } + if (!strncmp(v, "green", 5)) { *dark = 0x0A2814; *light = 0xE6F6E8; return; } + if (!strncmp(v, "white", 5)) { *dark = 0x141824; *light = 0xFFFFFF; return; } + if (!strncmp(v, "lightblue", 9)) { *dark = 0x0A1428; *light = 0xE5EEFB; return; } + if (!strncmp(v, "lightgreen", 10)) { *dark = 0x0A2814; *light = 0xE6F6E8; return; } + /* black / 其它未知 → 默认面 */ +} +static void apply_background(void) +{ + uint32_t ds, ls; + bg_surfaces(g_background, &ds, &ls); + for (int s = 0; s < 2; s++) { + g_theme[s][0].bg = g_theme[s][0].surface = ls; /* light 行 */ + g_theme[s][1].bg = g_theme[s][1].surface = ds; /* dark 行 */ + } +} + static void load_settings(void) { char buf[64]; @@ -117,6 +207,16 @@ static void load_settings(void) while (*v == ' ' || *v == '\t') v++; if (!strncmp(v, "miuix", 5) || !strncmp(v, "miui", 4)) g_style = MEOW_STYLE_MIUIX; } + + /* 背景全局源(§8.1.4):display.background 为 style×mode 下背景底选项集的当前值。 + * 读取缺失/未知 → ""(默认面)。 */ + g_background[0] = 0; + if (get_setting_str("display.background", g_background, sizeof g_background) != 0) + g_background[0] = 0; + + /* 载入/覆写 4 张主题表(色板 + style×mode 背景 surface/bg) */ + init_defaults(); + apply_background(); } /* 当前本地时刻的「当天第几分钟」(tm_hour*60+tm_min);供时段判定用(与 launcher s_now_min_of_day 一致)。 */ @@ -155,13 +255,21 @@ static void broadcast(void) if (g_listeners[i]) g_listeners[i](g_mode); } +/* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */ +static const meow_theme *theme_current(int style, int dark) +{ + if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3; + if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; + return &g_theme[style][dark ? 1 : 0]; +} + const meow_theme *meow_theme_get(void) { if (!g_loaded) { load_settings(); g_loaded = 1; } - return resolve_mode(g_mode) == MEOW_THEME_DARK ? &g_dark : &g_light; + return theme_current(g_style, resolve_mode(g_mode) == MEOW_THEME_DARK); } meow_theme_mode meow_theme_get_mode(void) @@ -189,11 +297,9 @@ void meow_theme_reload(void) meow_theme meow_theme_defaults(meow_theme_mode m) { - switch (resolve_mode(m)) { - case MEOW_THEME_DARK: return g_dark; - case MEOW_THEME_LIGHT: - default: return g_light; - } + if (!g_loaded) { load_settings(); g_loaded = 1; } + /* 脱机取内置板:返回 MD3(默认)style 下解析出的模式表。 */ + return g_theme[MEOW_STYLE_MD3][resolve_mode(m) == MEOW_THEME_DARK ? 1 : 0]; } /* ---- §8.1.2 顶级主题风格族(minimal)---- @@ -213,8 +319,9 @@ void meow_ui_set_style(int style) if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; if (style == g_style) return; g_style = style; - /* 触发下一帧按新 style 重绘:清 settings 缓存(下次 get 重读)不必要(style 已记录), - * 仅广播主题 listener(沿用 theme 广播机制,让控件/状态栏下一帧用新 style 重绘)。 */ + /* 触发下一帧按新 style 重绘(§8.1.2):g_theme[s][*] 是独立表(不随 g_style 重算), + * g_style 记录后 meow_theme_get() 立即可取新 style×mode 表;只需广播主题 listener, + * 让 App/状态栏/控件下一帧用新 style 重绘/换图标集。不清 settings 缓存(避免覆盖程序化 style)。 */ broadcast(); } diff --git a/meow_ui_widget.c b/meow_ui_widget.c index 00c8f77..dd5d8e0 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -9,7 +9,11 @@ #include #include -enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; +enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, + W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6 }; + +/* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */ +#define MEOW_LAYOUT_MAX_CHILD 32 /* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ @@ -30,14 +34,105 @@ struct meow_widget { int x, y, w, h; int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */ char label[128]; - int pressed; /* 按钮按下视觉态 */ + int pressed; /* 按钮按下视觉态(遗留字面;状态机用 MEOW_WS_PRESSED 位) */ int min, max, val; /* 滑条 */ int on; /* 开关 */ int count, sel; /* 列表 */ meow_list_source src; void *ctx; + + /* §5.8 batch1:统一状态机(§5.8.3)+ 通用 Options(§5.8.2)+ 自定义回调(§5.8.1)+ 布局(§5.8.5) */ + unsigned state; /* MEOW_WS_* 位掩码(默认 MEOW_WS_ENABLED) */ + meow_widget_opts opts; /* 通用修饰(浅拷;opts_used==0 时按控件内建默认) */ + int opts_used; /* 1=set_opts 显式挂载过 */ + meow_widget_render_fn render_cb; /* W_CUSTOM:自绘回调 */ + meow_widget_hit_fn hit_cb; /* W_CUSTOM:自定义命中回调 */ + int kind; /* W_LAYOUT:MEOW_LAYOUT_ROW/COLUMN/BOX */ + int spacing; /* W_LAYOUT:项间间距 */ + meow_widget *children[MEOW_LAYOUT_MAX_CHILD]; /* W_LAYOUT:子控件 */ + int nchildren; }; +/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- + * 注意:必须先定义 struct meow_widget,才能访问其成员(C 要求类型完整)。 */ +static uint32_t widget_bg(const meow_widget *w, const meow_theme *th, uint32_t def) +{ (void)th; return (w->opts_used && w->opts.bg) ? w->opts.bg : def; } +static uint32_t widget_fg(const meow_widget *w, const meow_theme *th, uint32_t def) +{ (void)th; return (w->opts_used && w->opts.fg) ? w->opts.fg : def; } +static int widget_radius(const meow_widget *w, int def) +{ return (w->opts_used && w->opts.radius >= 0) ? w->opts.radius : def; } +static int widget_pady(const meow_widget *w) +{ return (w->opts_used && w->opts.pad_y > 0) ? w->opts.pad_y : 0; } + +/* 是否禁用(状态机:MEOW_WS_ENABLED 未置位)。 */ +static int widget_disabled(const meow_widget *w) { return !(w->state & MEOW_WS_ENABLED); } + +/* MD3 状态层叠色:把基色向 onSurface 叠 t% 调出按下/按住/聚焦状态层(§5.8.4)。 */ +static uint32_t state_tint(uint32_t base, const meow_theme *th, int t) +{ return blend_color(base, th->onSurface, t); } + +/* ---- §5.8.2/§5.8.3 公共 API:Options + 状态机 ---- */ +int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts) +{ + if (!w || !opts) return -1; + w->opts = *opts; + w->opts_used = 1; + /* disabled/selected 立即反映到状态机位(§5.8.3)。 */ + if (opts->disabled) w->state &= ~MEOW_WS_ENABLED; + else w->state |= MEOW_WS_ENABLED; + if (opts->selected) w->state |= MEOW_WS_SELECTED; + else w->state &= ~MEOW_WS_SELECTED; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 挂载会改变绘制:标区域脏 */ + return 0; +} + +int meow_widget_opts_get(const meow_widget *w, meow_widget_opts *out) +{ + if (!w || !out) return -1; + if (!w->opts_used) { + /* 默认视图:radius=-1(控件默认)、disabled/selected 取状态位。 */ + memset(out, 0, sizeof *out); + out->radius = -1; + out->disabled = widget_disabled(w) ? 1 : 0; + out->selected = (w->state & MEOW_WS_SELECTED) ? 1 : 0; + return 0; + } + *out = w->opts; + return 0; +} + +void meow_widget_set_state(meow_widget *w, unsigned bits, int on) +{ + if (!w) return; + if (on) w->state |= bits; + else w->state &= ~bits; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 状态变化会改变绘制 */ +} +unsigned meow_widget_state(const meow_widget *w) { return w ? w->state : MEOW_WS_ENABLED; } +int meow_widget_enabled(const meow_widget *w) { return w ? (w->state & MEOW_WS_ENABLED) != 0 : 0; } +void meow_widget_set_enabled(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_ENABLED, on ? 1 : 0); } +int meow_widget_selected(const meow_widget *w) { return w ? (w->state & MEOW_WS_SELECTED) != 0 : 0; } +void meow_widget_set_selected(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_SELECTED, on ? 1 : 0); } + +/* 一键叠状态层(自定义画布可选;标准控件由 SDK 自动叠,不调用):半透明状态层 + 焦点描边。 */ +void meow_widget_draw_state_layer(meow_widget *w, const meow_theme *th) +{ + if (!w || !th) return; + if (widget_disabled(w)) { + meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + return; /* 禁用不再叠按下/聚焦 */ + } + if (w->state & MEOW_WS_PRESSED) { + /* 向 onSurface 叠半透明状态层铺满控件矩形(按下/按住) */ + int t = (w->opts_used && w->opts.bg) ? 24 : 12; + meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, t * 255 / 100); + } + if (w->state & MEOW_WS_FOCUSED) { + int r = widget_radius(w, 10); + meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); + } +} + /* ---- 实时按压即时刷新(agent-119)---- * 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在 * on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/ @@ -103,6 +198,7 @@ void meow_widget_touch_refresh(void) * 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上 * 画出旧按钮的 accent 残留)。 */ if (g_live_btn && g_live_btn->owner != active) { + g_live_btn->state &= ~MEOW_WS_PRESSED; /* §5.8.3:清除按压态(跨页面不残留) */ g_live_btn = NULL; g_live_prev_hold = 0; } @@ -112,18 +208,29 @@ void meow_widget_touch_refresh(void) if (!down) { /* 释放:把最近按压按钮恢复为常态 */ - if (g_live_btn) { live_btn_redraw(g_live_btn); g_live_btn = NULL; } + if (g_live_btn) { + g_live_btn->state &= ~MEOW_WS_PRESSED; + live_btn_redraw(g_live_btn); + g_live_btn = NULL; + } g_live_prev_hold = 0; return; } if (hit != g_live_btn) { /* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */ - if (g_live_btn) live_btn_redraw(g_live_btn); + if (g_live_btn) { + g_live_btn->state &= ~MEOW_WS_PRESSED; + live_btn_redraw(g_live_btn); + } g_live_btn = hit; g_live_prev_hold = hold; - if (hit) live_btn_redraw(hit); + if (hit) { + if (widget_disabled(hit)) { g_live_btn = NULL; } /* 禁用不响应按压 */ + else { hit->state |= MEOW_WS_PRESSED; live_btn_redraw(hit); } + } } else if (hit && hold != g_live_prev_hold) { /* 同一按钮按住达阈值:升级到更强态 */ + hit->state |= MEOW_WS_PRESSED; live_btn_redraw(hit); g_live_prev_hold = hold; } @@ -135,6 +242,7 @@ meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label) if (!t) return NULL; t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */ + t->state = MEOW_WS_ENABLED; /* §5.8.3:默认 ENABLED */ if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } live_btn_register(t); return t; @@ -146,6 +254,7 @@ meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val) if (!t) return NULL; t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40; t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; return t; } @@ -156,7 +265,9 @@ meow_widget *meow_switch_create(int x, int y, int on) if (!t) return NULL; t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32; t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; t->on = on ? 1 : 0; + if (t->on) t->state |= MEOW_WS_SELECTED; /* §5.8.3:开关 on → selected */ return t; } @@ -167,6 +278,7 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count, if (!t) return NULL; t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; return t; } @@ -178,6 +290,107 @@ void meow_widget_free(meow_widget *w) free(w); } +/* ---- §5.8.1 自定义画布:App 提供 render/hit 回调,同一个 meow_widget 基座 ---- */ +meow_widget *meow_widget_custom_create(int x, int y, int w, int h, + meow_widget_render_fn render_cb, + meow_widget_hit_fn hit_cb, + void *ctx) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_CUSTOM; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); /* §6.1 ①:同既有控件绑当前 active Activity */ + t->state = MEOW_WS_ENABLED; + t->render_cb = render_cb; + t->hit_cb = hit_cb; + t->ctx = ctx; + return t; +} + +/* ---- §5.8.5 可组合布局:Row / Column / Box + Spacer ---- */ +meow_widget *meow_row_create(int x, int y, int w, int h, int spacing) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_ROW; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->spacing = spacing > 0 ? spacing : 0; + return t; +} +meow_widget *meow_column_create(int x, int y, int w, int h, int spacing) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_COLUMN; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->spacing = spacing > 0 ? spacing : 0; + return t; +} +meow_widget *meow_box_create(int x, int y, int w, int h) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_BOX; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->spacing = 0; + return t; +} +meow_widget *meow_layout_spacer(int w, int h) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_SPACER; t->x = 0; t->y = 0; t->w = w > 0 ? w : 0; t->h = h > 0 ? h : 0; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + return t; +} + +int meow_layout_add(meow_widget *layout, meow_widget *child) +{ + if (!layout || layout->type != W_LAYOUT || !child) return -1; + if (layout->nchildren >= MEOW_LAYOUT_MAX_CHILD) return -1; + for (int i = 0; i < layout->nchildren; i++) + if (layout->children[i] == child) return -1; /* 同子项去重 */ + layout->children[layout->nchildren++] = child; + return 0; +} + +/* 把子控件算成绝对坐标(写进各 child 的 x,y;w/h 固定尺寸,先不做弹性)。 + * Row:子项水平排(垂直居中于容器 h);Column:垂直排(水平居中);Box:叠放(各放容器左上角)。 + * 子项的 margin_x/margin_y(Options)参与排布;子项为布局时递归 arrange。 */ +void meow_layout_arrange(meow_widget *layout) +{ + if (!layout || layout->type != W_LAYOUT) return; + int kind = layout->kind; + int cur = (kind == MEOW_LAYOUT_COLUMN) ? layout->y : layout->x; + for (int i = 0; i < layout->nchildren; i++) { + meow_widget *c = layout->children[i]; + if (!c) continue; + int mx = (c->opts_used && c->opts.margin_x > 0) ? c->opts.margin_x : 0; + int my = (c->opts_used && c->opts.margin_y > 0) ? c->opts.margin_y : 0; + if (kind == MEOW_LAYOUT_ROW) { + cur += mx; + c->x = cur; + c->y = layout->y + (layout->h - c->h) / 2; /* 垂直居中 */ + if (c->y < layout->y) c->y = layout->y; + cur += c->w + mx + layout->spacing; + } else if (kind == MEOW_LAYOUT_COLUMN) { + cur += my; + c->y = cur; + c->x = layout->x + (layout->w - c->w) / 2; /* 水平居中 */ + if (c->x < layout->x) c->x = layout->x; + cur += c->h + my + layout->spacing; + } else { /* BOX:叠放,各放容器左上角 */ + c->x = layout->x; + c->y = layout->y; + } + if (c->type == W_LAYOUT) meow_layout_arrange(c); /* 嵌套递归 */ + } +} + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -189,9 +402,15 @@ int meow_widget_hit(meow_widget *w, int x, int y) if (!w) return 0; /* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */ if (w->owner != meow_ui_activity_active()) return 0; + /* §5.8.3:禁用不响应命中(也不进 pressed)。 */ + if (widget_disabled(w)) return 0; + /* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);其余矩形内含。 */ + if (w->type == W_CUSTOM) + return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y); return hit(w, x, y); } -int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; } +int meow_btn_pressed(meow_widget *w) +{ return w && w->type == W_BTN ? (w->pressed || (w->state & MEOW_WS_PRESSED)) : 0; } void meow_slider_set(meow_widget *w, int val) { @@ -201,56 +420,86 @@ void meow_slider_set(meow_widget *w, int val) } int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; } void meow_switch_set(meow_widget *w, int on) -{ if (w && w->type == W_SWITCH) w->on = on ? 1 : 0; } +{ + if (w && w->type == W_SWITCH) { + w->on = on ? 1 : 0; + if (w->on) w->state |= MEOW_WS_SELECTED; /* §5.8.3:on → selected */ + else w->state &= ~MEOW_WS_SELECTED; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + } +} int meow_switch_get(meow_widget *w) { return w && w->type == W_SWITCH ? w->on : 0; } -/* 供应用在 on_tap 里对外可控回馈:这里不存命中态(由 app 分发),仅暴露绘制 */ +/* 供应用在 on_tap 里对外可控回馈:这里不存命中态(由 app 分发),仅暴露绘制。 + * §5.8 升级:统一取语义色(meow_theme),读 opts(背景/圆角/描边/pad/fg)与状态机位 + * (disabled/selected/pressed/focused),标准控件由 SDK 自动叠状态层;自定义画布(W_CUSTOM) + * 由 App render_cb 全权自绘(SDK 只注入主题、不画背景/状态层);布局(W_LAYOUT)递归绘制子控件。 */ void meow_widget_draw(meow_widget *w) { if (!w) return; const meow_theme *th = meow_theme_get(); + int disabled = widget_disabled(w); + switch (w->type) { case W_BTN: { - /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路: - * - w->pressed:应用在 on_tap 命中后标记(既有语义,保留); + /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源三路: + * - 状态机 MEOW_WS_PRESSED(App 经 set_state 或 SDK 触摸层置位); + * - w->pressed(既有字面,保留); * - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。 - * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。 */ + * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。禁用时不叠按下底。 */ int live = meow_input_touch_down() && meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; - int press = w->pressed || live; + int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live; int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; - /* 纯色状态层(无描边环、无分层):一层即满,深浅二态均由 accent 向黑调出。 - * 常态=accent、按下=accent+黑12%、按住=accent+黑24%。整体一层不「先底色再套圈」。 */ + if (disabled) press = 0; + /* 底色:选中态用 primaryContainer(onPrimaryContainer 文字);否则 primary(onPrimary 文字)。 + * 可被 opts.bg 覆盖(此时文字用 opts.fg 或 onPrimary)。 */ + int selected = (w->state & MEOW_WS_SELECTED); + uint32_t base, default_fg; + if (selected && !(w->opts_used && w->opts.bg)) { base = th->primaryContainer; default_fg = th->onPrimaryContainer; } + else { base = widget_bg(w, th, th->primary); default_fg = th->onPrimary; } uint32_t bg; - if (hold) - bg = blend_color(th->accent, 0x000000, 24); - else if (press) - bg = blend_color(th->accent, 0x000000, 12); - else - bg = th->accent; - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); - meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, - w->label, th->font_body, 0xFFFFFF); + if (hold) bg = state_tint(base, th, 24); /* 按住 = 深状态层 */ + else if (press) bg = state_tint(base, th, 12); /* 按下 = 状态层 */ + else bg = base; + int r = widget_radius(w, 10); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg); + /* 内容区受 pad 垂直内缩(水平居中文本,pad_x 不影响中心) */ + int pady = widget_pady(w); + int cx = w->x + w->w / 2; + int cy = w->y + pady + (w->h - 2 * pady - meow_ui_text_height(th->font_body)) / 2; + uint32_t tc = widget_fg(w, th, default_fg); + meow_ui_text_center(cx, cy, w->label, th->font_body, tc); + /* opts 描边 / 焦点外圈 */ + if (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) + meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, w->opts.stroke); + if (w->state & MEOW_WS_FOCUSED) + meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); + /* 禁用:整体变暗(状态层向 onSurface 叠 α) */ + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_SLIDER: { + /* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */ int trace_y = w->y + w->h / 2 - 3; - meow_ui_fill_rect(w->x, trace_y, w->w, 6, th->dim); + meow_ui_fill_rect(w->x, trace_y, w->w, 6, th->onSurfaceVariant); int range = w->max - w->min; int frac = range ? (w->val - w->min) * w->w / range : 0; - if (frac > 0) meow_ui_fill_rect(w->x, trace_y, frac, 6, th->accent); + if (frac > 0) meow_ui_fill_rect(w->x, trace_y, frac, 6, th->primary); int tx = w->x + frac; int ty = w->y + w->h / 2; - meow_gfx_fill_round(tx - 10, ty - 10, 20, 20, 10, th->fg); + meow_gfx_fill_round(tx - 10, ty - 10, 20, 20, 10, th->onSurface); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_SWITCH: { - uint32_t bg = w->on ? th->accent : th->dim; + uint32_t bg = w->on ? th->primary : th->onSurfaceVariant; meow_gfx_fill_round(w->x, w->y, w->w, w->h, w->h / 2, bg); int knob_r = w->h / 2 - 3; int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3; meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_LIST: { @@ -258,17 +507,31 @@ void meow_widget_draw(meow_widget *w) for (int i = 0; i < w->count; i++) { int ry = w->y + i * rowh; if (ry >= w->y + w->h) break; - if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->accent); + if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->primary); char label[64] = ""; if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) { - uint32_t col = (i == w->sel) ? 0xFFFFFF : th->fg; + uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2, label, th->font_body, col); } - meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->dim); + meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->onSurfaceVariant); } + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } - default: break; + case W_CUSTOM: + /* SDK 不替自定义画布画背景/状态层;App 在 render_cb 里自绘(th 供取色), + * 可读 meow_widget_state(状态位)/ meow_widget_opts_get(通用修饰)自行叠。 */ + if (w->render_cb) w->render_cb(w, th, w->ctx); + break; + case W_LAYOUT: + /* 递归绘制子控件(子控件坐标已由 meow_layout_arrange 算成绝对坐标)。 */ + for (int i = 0; i < w->nchildren; i++) + if (w->children[i]) meow_widget_draw(w->children[i]); + break; + case W_SPACER: + break; /* 不可见空白 */ + default: + break; } } From 7f36b6f069235c322c5ba461e6bdb8de6f5b78b3 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 22:33:51 +0800 Subject: [PATCH 16/49] =?UTF-8?q?feat(sdk):=20=E6=8E=A7=E4=BB=B6=E5=BA=93?= =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E6=89=B9=20Material=20=E5=AD=90=E9=9B=86(age?= =?UTF-8?q?nt-152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增控件: Button五型(filled/tonal/outlined/text/elevated)/TextField(接IME: round外框idle-outline/focused-primary+占位+光标)/Card/Divider/Badge/Checkbox/Radio/ProgressBar(线圆, value=-1不定)/FAB/TopAppBar(标题+‹返回+动作)/NavigationBar(TabBar pageSwitcher)+TabBar(下划线)/LazyColumn/LazyRow(虚拟化+scroll clamp) - Dialog(B③同构阻塞模态)/BottomSheet(B④), meow_ui_dialog.c - meow_gfx 新增 fill_circle/line/ring_arc(无-lm, COS90象限查表) - 结构体尾缀(对外opaque/calloc归零), 旧字段与create签名零破坏; meow_widget 类型扩到W_TEXTFIELD..W_FAB(17种) - meow_ui.a=18c21da5 hello=5e31d919 launcher=4952261d(重编重链无回归); build_sdk 0warning; 宿主 test_meow_ui_widgets 全PASS+test_sb_collapse PASS+PPM目检正确 - 与 §5.8/§5.2-5.7 一致; 遗留真机观感/性能 --- build_sdk.sh | 2 +- meow_ui.h | 114 +++++++ meow_ui_dialog.c | 293 ++++++++++++++++++ meow_ui_gfx.c | 100 ++++++- meow_ui_internal.h | 7 + meow_ui_widget.c | 664 +++++++++++++++++++++++++++++++++++++++-- test_host_stub.c | 24 ++ test_meow_ui_widgets.c | 180 +++++++++++ 8 files changed, 1360 insertions(+), 24 deletions(-) create mode 100644 meow_ui_dialog.c create mode 100644 test_host_stub.c create mode 100644 test_meow_ui_widgets.c diff --git a/build_sdk.sh b/build_sdk.sh index b0fe874..cecf0c5 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index 95b4821..a64dd49 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -403,6 +403,120 @@ void meow_layout_arrange(meow_widget *layout); /* 固定尺寸空白(也作撑余量;不可见,hit 恒 0)。 */ meow_widget *meow_layout_spacer(int w, int h); +/* ============================================================ */ +/* §5.8.6 Material 3 组件子集(第二批,基于第一批基座:Options + 状态机 + MD3 色板 + 布局) + * 每个组件 = 新增 meow_widget 类型或组合既有模板的便捷函数;复用主题语义色(th->primary / + * surfaceContainer / outline / ...)+ 风格参数(radius/radius_card/spacing/stroke_w)+ 通用 + * Options(meow_widget_set_opts 对它们同样生效)+ 状态机(enabled/selected/pressed/focused)。 + * 向后兼容:既有 create 签名一字不改;本批只在其上扩展。 */ +/* ============================================================ */ + +/* ---- Button 五型(meow_btn 扩展;既有 meow_btn_create = FILLED)---- */ +enum { + MEOW_BTN_FILLED = 0, /* primary 底 / onPrimary 文 */ + MEOW_BTN_TONAL = 1, /* secondaryContainer 底 / onSecondaryContainer 文 */ + MEOW_BTN_OUTLINED = 2, /* 透明底 / primary 文 / primary 描边 */ + MEOW_BTN_TEXT = 3, /* 透明底 / primary 文 / 无描边 */ + MEOW_BTN_ELEVATED = 4, /* surfaceContainerLow 底 / primary 文 */ +}; +meow_widget *meow_btn_create_variant(int x, int y, int w, int h, const char *label, int variant); +int meow_btn_set_variant(meow_widget *w, int variant); /* 越界 clamp;0=成功 */ + +/* ---- TextField(单行文本输入,接 IME §5.1)---- */ +/* placeholder=提示文案;buf=App 持有的文本缓冲(组件不回拷,只绘制/编辑其上);maxlen=含 NUL 预算 */ +meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, + char *buf, int maxlen, meow_ime_mode mode); +void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder); +/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);返回 1=确认 0=取消;buf 已回填 */ +int meow_textfield_activate(meow_widget *w); +const char *meow_textfield_text(meow_widget *w); + +/* ---- Card(圆角容器卡片;surfaceContainerLow 底 + 可选描边/状态层)---- */ +meow_widget *meow_card_create(int x, int y, int w, int h); +/* 内容子控件:绘制时递归绘到卡片内(卡片 hit 仍为矩形;子控件独立命中) */ +void meow_card_set_content(meow_widget *card, meow_widget *child); + +/* ---- Divider(分隔线;vertical=1 纵向)---- */ +meow_widget *meow_divider_create(int x, int y, int len, int vertical); + +/* ---- Badge(角标;count=0 红点 / >0 数字 / <0 隐藏;cx,cy=锚点=角标中心)---- */ +meow_widget *meow_badge_create(int cx, int cy); +void meow_badge_set(meow_widget *w, int count); + +/* ---- Checkbox / Radio(勾选态走 meow_widget_set_selected/meow_widget_selected)---- */ +meow_widget *meow_checkbox_create(int x, int y); +meow_widget *meow_radio_create(int x, int y); + +/* ---- ProgressBar(线性/圆形;value=0-100 确定性,-1=不定)---- */ +meow_widget *meow_progress_create(int x, int y, int w, int h, int value); /* 线性 */ +meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value); /* 圆形 */ +void meow_progress_set(meow_widget *w, int value); /* -1=不定 */ + +/* ---- FloatingActionButton(悬浮按钮;圆角胶囊,无投影仅状态层近似)---- */ +meow_widget *meow_fab_create(int x, int y, int size, const char *label); + +/* ---- TopAppBar(顶部标题栏:标题 + 返回 + 动作)---- */ +meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title); +void meow_topappbar_set_title(meow_widget *w, const char *title); +void meow_topappbar_set_back(meow_widget *w, int on); /* 1=显示返回箭头 */ +int meow_topappbar_add_action(meow_widget *w, const char *label); /* 返回动作下标 */ +#define MEOW_TOPBAR_BACK (-2) +int meow_topappbar_hit(meow_widget *w, int x, int y); /* MEOW_TOPBAR_BACK=返回 idx>=0=动作 -1=无 */ + +/* ---- NavigationBar(底部导航栏)---- */ +meow_widget *meow_navigationbar_create(int x, int y, int w, int h); +int meow_navigationbar_add_item(meow_widget *w, const char *label); +void meow_navigationbar_set_selected(meow_widget *w, int idx); +int meow_navigationbar_selected(meow_widget *w); +int meow_navigationbar_hit(meow_widget *w, int x, int y); /* 返回项下标/-1 */ + +/* ---- TabBar(顶部分页页签;swipe 联动 §5.2 由 App 在 on_swipe 驱动)---- */ +meow_widget *meow_tabbar_create(int x, int y, int w, int h); +int meow_tabbar_add_tab(meow_widget *w, const char *label); +void meow_tabbar_set_selected(meow_widget *w, int idx); +int meow_tabbar_hit(meow_widget *w, int x, int y); /* 返回页签下标/-1 */ + +/* ---- 列表容器(LazyColumn/LazyRow:数据源回调 + 可见区虚拟化;复用 meow_list_source) + * meow_lazycolumn_create=纵向(item_h=行高);meow_lazyrow_create=横向(item_w=列宽)。 + * 滚动由 App 在 on_swipe 里用 meow_list_scroll_by/set_scroll 驱动。 */ +meow_widget *meow_lazycolumn_create(int x, int y, int w, int h, int item_h, int count, + meow_list_source src, void *ctx); +meow_widget *meow_lazyrow_create(int x, int y, int w, int h, int item_w, int count, + meow_list_source src, void *ctx); +void meow_list_set_scroll(meow_widget *w, int offset); /* clamp 到 [0, max_scroll] */ +int meow_list_scroll(meow_widget *w); +int meow_list_max_scroll(meow_widget *w); /* max(0, count*item - viewport) */ +void meow_list_scroll_by(meow_widget *w, int dy); /* 相对滚动(App on_swipe 用) */ +int meow_list_set_item_h(meow_widget *w, int item); /* 改行高/列宽 */ +int meow_list_hit_index(meow_widget *w, int x, int y); /* 命中项下标(含滚动换算);-1=未命中项/越界 */ + +/* ============================================================ */ +/* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet + * 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构),控件化。 + * 阻塞式(内层 meow_input_pop 轮询,与 meow_ui_text_input 同构);打开前排空残留触摸。 + * 关闭途径:点卡片内选项=确认、点卡片外=取消、左缘返回手势=取消。不压状态栏(y 钳制)。 */ +/* ============================================================ */ + +/* ---- Dialog(弹窗:确认/选择/输入/信息)---- */ +typedef enum { MEOW_DLG_CONFIRM, MEOW_DLG_SELECT, MEOW_DLG_INPUT, MEOW_DLG_INFO } meow_dlg_t; +typedef struct meow_dialog meow_dialog; +meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title); +void meow_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur); /* SELECT 型 */ +void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime); /* INPUT 型 */ +void meow_dialog_show(meow_dialog *d); /* 阻塞到确认/取消 */ +int meow_dialog_result(meow_dialog *d); /* SELECT=选项下标;CONFIRM/INFO 确认=1、取消=0 */ +void meow_dialog_close(meow_dialog *d); /* 程序化关闭(App 亦可驱动) */ +void meow_dialog_free(meow_dialog *d); +/* CONFIRM 型便捷:点「确定」返回 1,取消/遮罩=0 */ +int meow_dialog_confirm(meow_dialog *d); + +/* ---- BottomSheet(底部面板:标题 + 选项列表;open 阻塞返回选中 idx/-1)---- */ +typedef struct meow_sheet meow_sheet; +meow_sheet *meow_sheet_create(const char *title, int h); +int meow_sheet_add_item(meow_sheet *s, const char *label); /* 返回项下标 */ +int meow_sheet_open(meow_sheet *s); /* 阻塞;返回选中 idx / -1=取消 */ +void meow_sheet_free(meow_sheet *s); + /* ============================================================ */ /* §9 应用日志(统一日志 API,M2.51 最小可用版) */ /* ============================================================ */ diff --git a/meow_ui_dialog.c b/meow_ui_dialog.c new file mode 100644 index 0000000..3facc68 --- /dev/null +++ b/meow_ui_dialog.c @@ -0,0 +1,293 @@ +/* + * meow_ui_dialog.c — §5.8.6 模态控件化(App 层,非系统叠加):Dialog / BottomSheet。 + * + * 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构):遮罩压暗 + + * 圆角卡片 + 标题 + 内容 + 操作;返回手势=取消;不压状态栏。阻塞式(内层 meow_input_pop + * 轮询,与 meow_ui_text_input 同构),打开前排空残留触摸(goodix 长尾免疫)。 + * 命中一律用抬手最新坐标;即画即显(打开一次性绘制,仅高亮变化才重绘,逐帧不重画静态内容)。 + * + * 属 App 层模态:不注册进系统顶条/通知中心渲染(系统级控件归属不动)。物理键在 B1 未下发 + * 给 App(on_key 无键),故本层关闭途径=触摸 + 左缘返回手势;音量/电源高亮导航为后续批次。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include + +#define MEOW_MODAL_MAX_ITEMS 32 +#define MEOW_MODAL_ROW 48 +#define MEOW_MODAL_BTN 44 + +/* ---- Dialog ---- */ +struct meow_dialog { + meow_dlg_t type; + char title[96]; + char items[MEOW_MODAL_MAX_ITEMS][64]; + int nitems; + int cur; /* SELECT 预选/高亮项 */ + int result; + int done; + meow_ime_ctx ime; /* INPUT 型(拷贝) */ + int has_ime; +}; + +/* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */ +typedef struct { int dx,dy,dw,dh; int row_y,rowh; int btn_y,btn_w,btn_h; } dlg_geom; + +static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g) +{ + int W = meow_ui_width(), H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24; + int tH = meow_ui_text_height(th->font_title); + int bH = meow_ui_text_height(th->font_body); + int select = (d->type == MEOW_DLG_SELECT); + int nrows = select ? d->nitems : 0; + int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1) + : (select ? 0 : (d->nitems > 0 ? d->nitems : 1)); + int have_btns = !select; + g->dw = 348; if (g->dw > W - 40) g->dw = W - 40; + g->dx = (W - g->dw) / 2; + int content = nrows * MEOW_MODAL_ROW + nmsg * (bH + 8); + g->dh = pad + tH + 12 + content + pad + (have_btns ? 16 + MEOW_MODAL_BTN : 0); + int avail = H - sb - 20; + if (g->dh > avail) g->dh = avail; + g->dy = (H - g->dh) / 2; if (g->dy < sb) g->dy = sb; + g->row_y = g->dy + pad + tH + 12; + g->rowh = (MEOW_MODAL_ROW < 0) ? 40 : MEOW_MODAL_ROW; + g->btn_y = g->row_y + content + 16; + g->btn_h = MEOW_MODAL_BTN; + g->btn_w = (g->dw - 2 * pad - 12) / 2; +} + +static void dlg_paint(meow_dialog *d, const meow_theme *th) +{ + int W = meow_ui_width(), H = meow_ui_height(); + meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); /* 遮罩压暗 */ + dlg_geom g; + dlg_calc(d, th, &g); + int pad = 24; + meow_gfx_fill_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->surfaceContainerHighest); + meow_ui_draw_rect_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->outline); + meow_ui_text(g.dx + pad, g.dy + pad, d->title, th->font_title, th->onSurface); + + if (d->type == MEOW_DLG_SELECT) { + for (int i = 0; i < d->nitems; i++) { + int ry = g.row_y + i * g.rowh; + if (ry + g.rowh > g.dy + g.dh - 12) break; + if (i == d->cur) { + meow_ui_fill_rect(g.dx + 8, ry, g.dw - 16, g.rowh, th->primary); + meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + d->items[i], th->font_body, 0xFFFFFF); + } else { + meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + d->items[i], th->font_body, th->onSurface); + } + meow_ui_fill_rect(g.dx + pad, ry + g.rowh - 1, g.dw - 2 * pad, 1, th->outlineVariant); + } + } else { + /* INFO/CONFIRM:正文行;INPUT:文本预览 */ + int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1) + : (d->nitems > 0 ? d->nitems : 1); + int y = g.row_y; + if (d->type == MEOW_DLG_INPUT) { + char buf[300]; + snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)"); + meow_ui_text(g.dx + pad, y, buf, th->font_body, th->onSurfaceVariant); + } else { + for (int i = 0; i < nmsg; i++) { + if (i < d->nitems && d->items[i][0]) + meow_ui_text(g.dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant); + y += meow_ui_text_height(th->font_body) + 8; + } + } + /* 取消(左,text 型)/ 确定(右,filled) */ + int bx = g.dx + pad, cx = bx + g.btn_w + 12, by = g.btn_y; + meow_ui_draw_rect_round(bx, by, g.btn_w, g.btn_h, 10, th->primary); + meow_ui_text_center(bx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + "CANCEL", th->font_body, th->primary); + meow_gfx_fill_round(cx, by, g.btn_w, g.btn_h, 10, th->primary); + meow_ui_text_center(cx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + "OK", th->font_body, th->onPrimary); + } +} + +meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title) +{ + meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d); + if (!d) return NULL; + d->type = t; + if (title) { strncpy(d->title, title, sizeof d->title - 1); d->title[sizeof d->title - 1] = 0; } + d->cur = -1; d->result = 0; d->done = 0; + return d; +} +void meow_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur) +{ + if (!d) return; + d->nitems = 0; + if (item) for (int i = 0; i < n && i < MEOW_MODAL_MAX_ITEMS; i++) + if (item[i]) { strncpy(d->items[d->nitems], item[i], 63); d->items[d->nitems][63] = 0; d->nitems++; } + d->cur = cur; +} +void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime) +{ + if (!d || !ime) return; + d->ime = *ime; + d->has_ime = 1; +} + +void meow_dialog_close(meow_dialog *d) +{ + if (d) d->done = 1; +} + +void meow_dialog_show(meow_dialog *d) +{ + if (!d) return; + const meow_theme *th = meow_theme_get(); + /* INPUT 型若未带 ime,用空 ctx 兜底 */ + if (d->type == MEOW_DLG_INPUT && !d->has_ime) { + memset(&d->ime, 0, sizeof d->ime); + d->ime.maxlen = 255; + } + meow_input_start(); + meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ + d->done = 0; d->result = 0; + dlg_paint(d, th); + meow_ui_flush(); + + while (!d->done) { + meow_input_ev e; + while (meow_input_pop(&e)) { + if (e.type == MEOW_EV_TAP) { + int x = e.a, y = e.b; + dlg_geom g; + dlg_calc(d, th, &g); + int in_card = x >= g.dx && x < g.dx + g.dw && y >= g.dy && y < g.dy + g.dh; + if (!in_card) { d->done = 1; d->result = 0; break; } /* 遮罩外=取消 */ + if (d->type == MEOW_DLG_SELECT) { + int idx = (y - g.row_y) / g.rowh; + if (idx >= 0 && idx < d->nitems) { d->cur = idx; d->result = idx; d->done = 1; } + break; + } + /* 非选择:按钮命中 */ + int pad = 24, by = g.btn_y; + int bx = g.dx + pad, cx = bx + g.btn_w + 12; + if (y >= by && y < by + g.btn_h) { + if (x >= bx && x < bx + g.btn_w) { /* CANCEL */ + d->done = 1; d->result = 0; + } else if (x >= cx && x < cx + g.btn_w) { /* OK */ + if (d->type == MEOW_DLG_INPUT) { + d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */ + } else { + d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1; + } + d->done = 1; + } + break; + } + } else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) { + d->done = 1; d->result = 0; /* 返回=取消 */ + } + } + if (d->done) break; + usleep(16000); + } +} + +int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; } +int meow_dialog_confirm(meow_dialog *d) +{ + if (!d || d->type != MEOW_DLG_CONFIRM) return 0; + meow_dialog_show(d); + return d->result == 1; +} +void meow_dialog_free(meow_dialog *d) { if (d) free(d); } + +/* ---- BottomSheet ---- */ +struct meow_sheet { + char title[96]; + char items[MEOW_MODAL_MAX_ITEMS][64]; + int nitems; + int h; + int result; + int done; +}; + +static int sheet_h(meow_sheet *s, const meow_theme *th) +{ + int H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int avail = H - sb - 20; + int title_h = meow_ui_text_height(th->font_title); + int min_h = 24 + title_h + 12 + MEOW_MODAL_ROW + 20; + int want = (s->h > 0) ? s->h : min_h; + int est = 24 + title_h + 12 + s->nitems * MEOW_MODAL_ROW + 20; + if (est > want) want = est; + if (want > avail) want = avail; + if (want < min_h) want = min_h < avail ? min_h : avail; + return want; +} + +meow_sheet *meow_sheet_create(const char *title, int h) +{ + meow_sheet *s = (meow_sheet *)calloc(1, sizeof *s); + if (!s) return NULL; + if (title) { strncpy(s->title, title, sizeof s->title - 1); s->title[sizeof s->title - 1] = 0; } + s->h = h > 0 ? h : 0; + return s; +} +int meow_sheet_add_item(meow_sheet *s, const char *label) +{ + if (!s || s->nitems >= MEOW_MODAL_MAX_ITEMS) return -1; + if (label) { strncpy(s->items[s->nitems], label, 63); s->items[s->nitems][63] = 0; } + int idx = s->nitems; + s->nitems++; + return idx; +} +int meow_sheet_open(meow_sheet *s) +{ + if (!s) return -1; + const meow_theme *th = meow_theme_get(); + meow_input_start(); + meow_input_drain(); + s->done = 0; s->result = -1; + int W = meow_ui_width(), H = meow_ui_height(); + int ph = sheet_h(s, th); + int py = H - ph, pad = 24; + /* 首次绘制:遮罩 + 底部面板(顶部圆角) */ + meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); + meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest); + meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface); + for (int i = 0; i < s->nitems; i++) { + int ry = py + pad + meow_ui_text_height(th->font_title) + 12 + i * MEOW_MODAL_ROW; + if (ry + MEOW_MODAL_ROW > H - 8) break; + meow_ui_text(pad, ry + (MEOW_MODAL_ROW - meow_ui_text_height(th->font_body)) / 2, + s->items[i], th->font_body, th->onSurface); + meow_ui_fill_rect(pad, ry + MEOW_MODAL_ROW - 1, W - 2 * pad, 1, th->outlineVariant); + } + meow_ui_flush(); + + while (!s->done) { + meow_input_ev e; + while (meow_input_pop(&e)) { + if (e.type == MEOW_EV_TAP) { + int y = e.b; + if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外=取消 */ + int title_h = meow_ui_text_height(th->font_title); + int cy = py + pad + title_h + 12; + int idx = (y - cy) / MEOW_MODAL_ROW; + if (idx >= 0 && idx < s->nitems) { s->result = idx; s->done = 1; } + break; + } else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) { + s->done = 1; s->result = -1; + } + } + if (s->done) break; + usleep(16000); + } + return s->result; +} +void meow_sheet_free(meow_sheet *s) { if (s) free(s); } diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index fc359cc..b3b3ba5 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -311,7 +311,6 @@ void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color) { if (!g_init_ok) return; if (w <= 0 || h <= 0) return; - int x0 = x, y0 = y; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x >= g_xres || y >= g_yres) return; @@ -394,6 +393,105 @@ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color) dirty_add(x1, y1, x2 - x1, y2 - y1); } +/* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) ================= + * 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、 + * 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */ +static const int m_cos90[91] = { + 1024,1024,1023,1023,1022,1020,1018,1016, + 1014,1011,1008,1005,1002,998,994,989, + 984,979,974,968,962,956,949,943, + 935,928,920,912,904,896,887,878, + 868,859,849,839,828,818,807,796, + 784,773,761,749,737,724,711,698, + 685,672,658,644,630,616,602,587, + 573,558,543,527,512,496,481,465, + 449,433,416,400,384,367,350,333, + 316,299,282,265,248,230,213,195, + 178,160,143,125,107,89,71,54, + 36,18,0, +}; + +static int m_cos_deg(int deg) /* 任意角度(可负)→ cos(deg)*1024 */ +{ + int a = deg % 360; + if (a < 0) a += 360; + if (a <= 90) return m_cos90[a]; + if (a <= 180) return -m_cos90[180 - a]; + if (a <= 270) return -m_cos90[a - 180]; + return m_cos90[360 - a]; +} +static int m_sin_deg(int deg) { return m_cos_deg(90 - deg); } + +void meow_gfx_fill_circle(int cx, int cy, int r, uint32_t color) +{ + if (!g_init_ok || r <= 0) return; + int x1 = cx - r, y1 = cy - r, x2 = cx + r + 1, y2 = cy + r + 1; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + if (x2 <= x1 || y2 <= y1) return; + int r2 = r * r; + for (int py = y1; py < y2; py++) { + int dy = py - cy; + for (int px = x1; px < x2; px++) { + int dx = px - cx; + if (dx * dx + dy * dy <= r2) put_px(px, py, color); + } + } + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + +void meow_gfx_line(int x0, int y0, int x1, int y1, int th, uint32_t color) +{ + if (!g_init_ok) return; + int dx = x1 - x0, dy = y1 - y0; + int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy; + int steps = (adx > ady ? adx : ady) + 1; + if (steps <= 1) { meow_gfx_fill_circle(x0, y0, th / 2 + 1, color); return; } + int cap = th / 2; + if (cap < 1) cap = 1; + for (int i = 0; i <= steps; i++) { + int x = x0 + dx * i / steps; + int y = y0 + dy * i / steps; + if (cap <= 1) put_px(x, y, color); + else meow_gfx_fill_circle(x, y, cap, color); + } +} + +void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_deg, uint32_t color) +{ + if (!g_init_ok || r <= 0) return; + int sw = sweep_deg % 360; + if (sw < 0) sw += 360; + if (sw >= 360) { /* 整环:bbox 距离带(无角度判定,最快) */ + int r1 = r - th / 2, r2 = r + th / 2; + if (r1 < 0) r1 = 0; + int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + if (x2 <= x1 || y2 <= y1) return; + for (int py = y1; py < y2; py++) + for (int px = x1; px < x2; px++) { + int dx = px - cx, dy = py - cy; + int d2 = dx * dx + dy * dy; + if (d2 >= r1 * r1 && d2 <= r2 * r2) put_px(px, py, color); + } + dirty_add(x1, y1, x2 - x1, y2 - y1); + return; + } + /* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */ + int n = sw / 2 + 1; + if (n < 2) n = 2; + int px = cx + (m_cos_deg(start_deg) * r) / 1024; + int py = cy - (m_sin_deg(start_deg) * r) / 1024; /* 屏幕 y 向下 → sin 取负 */ + for (int i = 1; i <= n; i++) { + int deg = start_deg + sw * i / n; + int nx = cx + (m_cos_deg(deg) * r) / 1024; + int ny = cy - (m_sin_deg(deg) * r) / 1024; + meow_gfx_line(px, py, nx, ny, th, color); + px = nx; py = ny; + } +} + void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha) { if (!g_init_ok) return; diff --git a/meow_ui_internal.h b/meow_ui_internal.h index a38a059..9a4a610 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -112,6 +112,13 @@ unsigned char *meow_ui_back_buf(void); /* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */ void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color); +/* §5.8.6 batch2:软渲圆形/线段/圆弧环原语(无 GPU、无 -lm,内部整数/查表实现)。 + * 供 Radio(外圈/内点)、Badge(红点)、ProgressBar(环形轨道/进度弧)、Checkbox(勾线段)使用。 */ +void meow_gfx_fill_circle(int cx, int cy, int r, uint32_t color); /* 实心圆(bbox 距离测试) */ +void meow_gfx_line(int x0, int y0, int x1, int y1, int th, uint32_t color); /* 粗线(Bresenham + 圆帽) */ +void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, + int sweep_deg, uint32_t color); /* 圆弧环(查表采样成粗线) */ + /* 是否运行于真实 fb 之上(0 = 头less/离屏,qemu/宿主)。 */ int meow_gfx_is_device(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index dd5d8e0..f4f5bb3 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -8,13 +8,20 @@ #include "meow_ui_internal.h" #include #include +#include enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, - W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6 }; + W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6, + W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10, + W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13, + W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17 }; /* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */ #define MEOW_LAYOUT_MAX_CHILD 32 +/* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */ +#define MEOW_CONTAINER_MAX 8 + /* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u @@ -51,6 +58,21 @@ struct meow_widget { int spacing; /* W_LAYOUT:项间间距 */ meow_widget *children[MEOW_LAYOUT_MAX_CHILD]; /* W_LAYOUT:子控件 */ int nchildren; + + /* §5.8.6 batch2 Material 子集(尾缀追加;calloc 归零,安全) */ + int variant; /* W_BTN:MEOW_BTN_* 变体; W_PROGRESS:0=线性/1=圆形 */ + char placeholder[64]; /* W_TEXTFIELD 提示文案 */ + char *buf; int buflen; /* W_TEXTFIELD 文本缓冲(App 持有)+ 当前长 */ + int maxlen; /* W_TEXTFIELD 上限(含 NUL) */ + meow_ime_mode ime_mode; /* W_TEXTFIELD IME 模式 */ + int value; /* W_PROGRESS:0-100 / -1=不定; W_BADGE:计数 */ + int item_h; /* W_LIST:行高/列宽(lazy),默认 44 */ + int scroll; /* W_LIST:滚动偏移(lazy) */ + int horizontal; /* W_LIST:1=横向(LazyRow) */ + int back_on; /* W_TOPAPPBAR:显示返回箭头 */ + char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */ + int nitems; /* 上述项数 */ + int selected; /* W_NAVBAR / W_TABBAR 选中下标 */ }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- @@ -391,6 +413,384 @@ void meow_layout_arrange(meow_widget *layout) } } +/* ============================================================ */ +/* §5.8.6 batch2 Material 组件子集 API(create/setter/getter/hit) */ +/* ============================================================ */ + +/* ---- Button 五型(既有 meow_btn_create = FILLED) ---- */ +meow_widget *meow_btn_create_variant(int x, int y, int w, int h, const char *label, int variant) +{ + meow_widget *t = meow_btn_create(x, y, w, h, label); + if (!t) return NULL; + if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) variant = MEOW_BTN_FILLED; + t->variant = variant; + return t; +} +int meow_btn_set_variant(meow_widget *w, int variant) +{ + if (!w || w->type != W_BTN) return -1; + if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) return -1; + if (variant == w->variant) return 0; + w->variant = variant; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return 0; +} + +/* ---- TextField ---- */ +meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, + char *buf, int maxlen, meow_ime_mode mode) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_TEXTFIELD; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + if (placeholder) { + strncpy(t->placeholder, placeholder, sizeof t->placeholder - 1); + t->placeholder[sizeof t->placeholder - 1] = 0; + } + t->buf = buf; t->buflen = buf ? (int)strlen(buf) : 0; + t->maxlen = (maxlen > 0 && maxlen <= 256) ? maxlen : 256; + t->ime_mode = mode; + return t; +} +void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder) +{ + if (!w || w->type != W_TEXTFIELD) return; + if (placeholder) { + strncpy(w->placeholder, placeholder, sizeof w->placeholder - 1); + w->placeholder[sizeof w->placeholder - 1] = 0; + } + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +const char *meow_textfield_text(meow_widget *w) { return (w && w->type == W_TEXTFIELD && w->buf) ? w->buf : ""; } +/* 输入中实时把 IME 文本同步到字段缓冲 + 只刷字段区(字段在顶部,IME 键盘在底部覆盖层) */ +static void textfield_change_cb(const char *text, void *ctx) +{ + meow_widget *w = (meow_widget *)ctx; + if (!w || w->type != W_TEXTFIELD) return; + if (w->buf && text) { + strncpy(w->buf, text, w->maxlen - 1); + w->buf[w->maxlen - 1] = 0; + } + w->buflen = text ? (int)strlen(text) : 0; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_textfield_activate(meow_widget *w) +{ + if (!w || w->type != W_TEXTFIELD) return 0; + w->state |= MEOW_WS_FOCUSED; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + meow_ime_ctx ctx; + memset(&ctx, 0, sizeof ctx); + ctx.mode = w->ime_mode; + ctx.maxlen = w->maxlen; + ctx.on_change = textfield_change_cb; + ctx.ctx = w; + if (w->buf) strncpy(ctx.buf, w->buf, sizeof ctx.buf - 1); /* 预填当前文本 */ + int r = meow_ui_text_input(&ctx); + if (r && w->buf) { + strncpy(w->buf, ctx.buf, w->maxlen - 1); + w->buf[w->maxlen - 1] = 0; + } + w->buflen = w->buf ? (int)strlen(w->buf) : 0; + w->state &= ~MEOW_WS_FOCUSED; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return r; +} + +/* ---- Card ---- */ +meow_widget *meow_card_create(int x, int y, int w, int h) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_CARD; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + return t; +} +void meow_card_set_content(meow_widget *card, meow_widget *child) +{ + if (!card || card->type != W_CARD || !child) return; + card->children[0] = child; + card->nchildren = 1; +} + +/* ---- Divider ---- */ +meow_widget *meow_divider_create(int x, int y, int len, int vertical) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_DIVIDER; t->x = x; t->y = y; + t->w = vertical ? 1 : (len > 0 ? len : 1); + t->h = vertical ? (len > 0 ? len : 1) : 1; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->variant = vertical ? 1 : 0; + return t; +} + +/* ---- Badge ---- */ +meow_widget *meow_badge_create(int cx, int cy) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_BADGE; t->x = cx; t->y = cy; t->w = 24; t->h = 24; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->value = 0; /* 红点 */ + return t; +} +void meow_badge_set(meow_widget *w, int count) +{ + if (!w || w->type != W_BADGE) return; + w->value = count; + meow_gfx_mark_dirty(w->x - 16, w->y - 16, 32, 32); +} + +/* ---- Checkbox / Radio ---- */ +meow_widget *meow_checkbox_create(int x, int y) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_CHECKBOX; t->x = x; t->y = y; t->w = 34; t->h = 34; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + return t; +} +meow_widget *meow_radio_create(int x, int y) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_RADIO; t->x = x; t->y = y; t->w = 34; t->h = 34; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + return t; +} + +/* ---- ProgressBar ---- */ +meow_widget *meow_progress_create(int x, int y, int w, int h, int value) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_PROGRESS; t->x = x; t->y = y; t->w = w; t->h = h > 0 ? h : 4; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->variant = 0; /* 线性 */ + t->value = value < 0 ? -1 : (value > 100 ? 100 : value); + return t; +} +meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_PROGRESS; t->x = cx; t->y = cy; t->w = r; t->h = r; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->variant = 1; /* 圆形 */ + t->value = value < 0 ? -1 : (value > 100 ? 100 : value); + return t; +} +void meow_progress_set(meow_widget *w, int value) +{ + if (!w || w->type != W_PROGRESS) return; + w->value = value < 0 ? -1 : (value > 100 ? 100 : value); + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} + +/* ---- FAB ---- */ +meow_widget *meow_fab_create(int x, int y, int size, const char *label) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_FAB; t->x = x; t->y = y; t->w = size > 0 ? size : 48; t->h = t->w; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + return t; +} + +/* ---- TopAppBar ---- */ +meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_TOPAPPBAR; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->back_on = 0; t->nitems = 0; + if (title) { strncpy(t->label, title, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + return t; +} +void meow_topappbar_set_title(meow_widget *w, const char *title) +{ + if (!w || w->type != W_TOPAPPBAR) return; + if (title) { strncpy(w->label, title, sizeof w->label - 1); w->label[sizeof w->label - 1] = 0; } + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +void meow_topappbar_set_back(meow_widget *w, int on) +{ + if (!w || w->type != W_TOPAPPBAR) return; + w->back_on = on ? 1 : 0; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_topappbar_add_action(meow_widget *w, const char *label) +{ + if (!w || w->type != W_TOPAPPBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1; + if (label) strncpy(w->items[w->nitems], label, 23); + w->items[w->nitems][23] = 0; + int idx = w->nitems; + w->nitems++; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return idx; +} +int meow_topappbar_hit(meow_widget *w, int x, int y) +{ + if (!w || w->type != W_TOPAPPBAR) return -1; + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1; + if (w->back_on && x >= w->x && x < w->x + 44) return MEOW_TOPBAR_BACK; + const meow_theme *th = meow_theme_get(); + int ax = w->x + w->w - 10; + for (int i = w->nitems - 1; i >= 0; i--) { + int aw = meow_ui_text_width(w->items[i], th->font_body) + 12; + ax -= aw; + if (x >= ax && x < ax + aw && x >= w->x + 100) return i; /* 不压标题区/返回区 */ + } + return -1; +} + +/* ---- NavigationBar ---- */ +meow_widget *meow_navigationbar_create(int x, int y, int w, int h) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_NAVBAR; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->nitems = 0; t->selected = 0; + return t; +} +int meow_navigationbar_add_item(meow_widget *w, const char *label) +{ + if (!w || w->type != W_NAVBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1; + if (label) strncpy(w->items[w->nitems], label, 23); + w->items[w->nitems][23] = 0; + int idx = w->nitems; + w->nitems++; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return idx; +} +void meow_navigationbar_set_selected(meow_widget *w, int idx) +{ + if (!w || w->type != W_NAVBAR) return; + if (idx < 0) idx = 0; + if (idx >= w->nitems) idx = w->nitems - 1; + w->selected = idx; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_navigationbar_selected(meow_widget *w) { return (w && w->type == W_NAVBAR) ? w->selected : 0; } +int meow_navigationbar_hit(meow_widget *w, int x, int y) +{ + if (!w || w->type != W_NAVBAR || w->nitems <= 0) return -1; + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1; + int iw = w->w / w->nitems; + int idx = iw > 0 ? (x - w->x) / iw : 0; + return (idx >= 0 && idx < w->nitems) ? idx : -1; +} + +/* ---- TabBar ---- */ +meow_widget *meow_tabbar_create(int x, int y, int w, int h) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_TABBAR; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->nitems = 0; t->selected = 0; + return t; +} +int meow_tabbar_add_tab(meow_widget *w, const char *label) +{ + if (!w || w->type != W_TABBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1; + if (label) strncpy(w->items[w->nitems], label, 23); + w->items[w->nitems][23] = 0; + int idx = w->nitems; + w->nitems++; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return idx; +} +void meow_tabbar_set_selected(meow_widget *w, int idx) +{ + if (!w || w->type != W_TABBAR) return; + if (idx < 0) idx = 0; + if (idx >= w->nitems) idx = w->nitems - 1; + w->selected = idx; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_tabbar_hit(meow_widget *w, int x, int y) +{ + if (!w || w->type != W_TABBAR || w->nitems <= 0) return -1; + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1; + int iw = w->w / w->nitems; + int idx = iw > 0 ? (x - w->x) / iw : 0; + return (idx >= 0 && idx < w->nitems) ? idx : -1; +} + +/* ---- 列表容器(LazyColumn/LazyRow:虚拟化 + 滚动;复用 W_LIST 与 meow_list_source)---- */ +meow_widget *meow_lazycolumn_create(int x, int y, int w, int h, int item_h, int count, + meow_list_source src, void *ctx) +{ + meow_widget *t = meow_list_create(x, y, w, h, count, src, ctx); + if (!t) return NULL; + t->item_h = item_h > 0 ? item_h : 44; + t->scroll = 0; t->horizontal = 0; + return t; +} +meow_widget *meow_lazyrow_create(int x, int y, int w, int h, int item_w, int count, + meow_list_source src, void *ctx) +{ + meow_widget *t = meow_list_create(x, y, w, h, count, src, ctx); + if (!t) return NULL; + t->item_h = item_w > 0 ? item_w : 60; + t->scroll = 0; t->horizontal = 1; + return t; +} +static int list_item(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 44; } +int meow_list_max_scroll(meow_widget *w) +{ + if (!w || w->type != W_LIST) return 0; + int vp = w->horizontal ? w->w : w->h; + int ms = w->count * list_item(w) - vp; + return ms < 0 ? 0 : ms; +} +void meow_list_set_scroll(meow_widget *w, int offset) +{ + if (!w || w->type != W_LIST) return; + int maxs = meow_list_max_scroll(w); + if (offset < 0) offset = 0; + if (offset > maxs) offset = maxs; + if (offset == w->scroll) return; + w->scroll = offset; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_list_scroll(meow_widget *w) { return (w && w->type == W_LIST) ? w->scroll : 0; } +void meow_list_scroll_by(meow_widget *w, int dy) { if (w) meow_list_set_scroll(w, w->scroll + dy); } +int meow_list_set_item_h(meow_widget *w, int item) +{ + if (!w || w->type != W_LIST) return -1; + if (item > 0) { w->item_h = item; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); } + return w->item_h; +} +int meow_list_hit_index(meow_widget *w, int x, int y) +{ + if (!w || w->type != W_LIST) return -1; + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1; + int item = list_item(w); + int idx = w->horizontal ? (x - w->x + w->scroll) / item + : (y - w->y + w->scroll) / item; + return (idx >= 0 && idx < w->count) ? idx : -1; +} + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -453,27 +853,50 @@ void meow_widget_draw(meow_widget *w) int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live; int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; if (disabled) press = 0; - /* 底色:选中态用 primaryContainer(onPrimaryContainer 文字);否则 primary(onPrimary 文字)。 - * 可被 opts.bg 覆盖(此时文字用 opts.fg 或 onPrimary)。 */ + /* §5.8.6 五型:filled=primary/onPrimary;tonal=secondaryContainer/onSecondaryContainer; + * outlined=透明底+primary 文+outline 描边;text=透明底+primary 文;elevated=surfaceContainerLow+primary 文。 + * 选中态优先生效(primaryContainer / onPrimaryContainer);opts.bg/fg 可覆盖。 */ int selected = (w->state & MEOW_WS_SELECTED); - uint32_t base, default_fg; - if (selected && !(w->opts_used && w->opts.bg)) { base = th->primaryContainer; default_fg = th->onPrimaryContainer; } - else { base = widget_bg(w, th, th->primary); default_fg = th->onPrimary; } + int variant = w->variant; + if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) variant = MEOW_BTN_FILLED; + int fill = 1, stroke_draw = 0, stroke_w = 1; + uint32_t base, default_fg, stroke_col = 0; + if (selected && !(w->opts_used && w->opts.bg)) { + base = th->primaryContainer; default_fg = th->onPrimaryContainer; + } else { + switch (variant) { + case MEOW_BTN_TONAL: + base = th->secondaryContainer; default_fg = th->onSecondaryContainer; break; + case MEOW_BTN_OUTLINED: + fill = 0; base = 0; default_fg = th->primary; + stroke_draw = 1; stroke_col = th->outline; stroke_w = 1; break; + case MEOW_BTN_TEXT: + fill = 0; base = 0; default_fg = th->primary; break; + case MEOW_BTN_ELEVATED: + base = th->surfaceContainerLow; default_fg = th->primary; + stroke_draw = 1; stroke_col = th->outlineVariant; stroke_w = 1; break; + default: + base = th->primary; default_fg = th->onPrimary; break; + } + } + if (w->opts_used && w->opts.bg) { base = w->opts.bg; fill = 1; } + if (w->opts_used && w->opts.fg) default_fg = w->opts.fg; uint32_t bg; - if (hold) bg = state_tint(base, th, 24); /* 按住 = 深状态层 */ - else if (press) bg = state_tint(base, th, 12); /* 按下 = 状态层 */ - else bg = base; + if (fill) bg = (hold) ? state_tint(base, th, 24) : (press ? state_tint(base, th, 12) : base); + else bg = 0; int r = widget_radius(w, 10); - meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg); + if (fill) meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg); + else if (press) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 12); /* transparent ripple */ /* 内容区受 pad 垂直内缩(水平居中文本,pad_x 不影响中心) */ int pady = widget_pady(w); int cx = w->x + w->w / 2; int cy = w->y + pady + (w->h - 2 * pady - meow_ui_text_height(th->font_body)) / 2; - uint32_t tc = widget_fg(w, th, default_fg); + uint32_t tc = default_fg; meow_ui_text_center(cx, cy, w->label, th->font_body, tc); - /* opts 描边 / 焦点外圈 */ - if (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) - meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, w->opts.stroke); + /* opts 描边(优先)/ 变体内建描边 / 焦点外圈 */ + int sw = (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) ? w->opts.stroke_w : (stroke_draw ? stroke_w : 0); + uint32_t sc = (w->opts_used && w->opts.stroke) ? w->opts.stroke : stroke_col; + if (sw > 0 && sc) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, sc); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); /* 禁用:整体变暗(状态层向 onSurface 叠 α) */ @@ -503,18 +926,41 @@ void meow_widget_draw(meow_widget *w) break; } case W_LIST: { - int rowh = 44, pad = 16; - for (int i = 0; i < w->count; i++) { - int ry = w->y + i * rowh; - if (ry >= w->y + w->h) break; - if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->primary); + /* §5.8.6 列表容器(LazyColumn/LazyRow):只画可见项(虚拟化)+ 滚动偏移。 + * 纵向=行高 item_h(默认 44,兼容既有 meow_list_create);横向=LazyRow(列宽=item_h)。 */ + int pad = 16; + int item = list_item(w); + int vp = w->horizontal ? w->w : w->h; + int off = w->scroll; + if (off < 0) off = 0; + int maxs = w->count * item - vp; /* max_scroll(与 meow_list_max_scroll 同式) */ + if (maxs < 0) maxs = 0; + if (off > maxs) off = maxs; + int first = (item > 0) ? off / item : 0; + int last = (item > 0) ? (off + vp) / item + 1 : 0; + if (first < 0) first = 0; + if (last > w->count) last = w->count; + for (int i = first; i < last; i++) { char label[64] = ""; - if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) { + if (!w->src || !w->src(i, label, (int)sizeof label, w->ctx)) continue; + int pos = i * item - off; /* 相对容器起点的可视位置 */ + if (w->horizontal) { + int rx = w->x + pos; + if (rx >= w->x + w->w) break; + if (i == w->sel) meow_ui_fill_rect(rx, w->y, item, w->h, th->primary); uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; - meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2, + meow_ui_text_center(rx + item / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, + label, th->font_body, col); + meow_ui_fill_rect(rx + item - 1, w->y, 1, w->h, th->onSurfaceVariant); + } else { + int ry = w->y + pos; + if (ry >= w->y + w->h) break; + if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, item, th->primary); + uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; + meow_ui_text(w->x + pad, ry + (item - meow_ui_text_height(th->font_body)) / 2, label, th->font_body, col); + meow_ui_fill_rect(w->x, ry + item - 1, w->w, 1, th->onSurfaceVariant); } - meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->onSurfaceVariant); } if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; @@ -529,6 +975,180 @@ void meow_widget_draw(meow_widget *w) for (int i = 0; i < w->nchildren; i++) if (w->children[i]) meow_widget_draw(w->children[i]); break; + case W_TEXTFIELD: { + /* MD3 输入框:圆角外框(surfaceContainerLowest 底 + outline 描边;focused=primary 2px)+ 提示/文本 + 光标。 */ + int r = widget_radius(w, 8); + int focused = (w->state & MEOW_WS_FOCUSED) != 0; + meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, th->surfaceContainerLowest); + uint32_t bcol = focused ? th->primary : th->outline; + int bw = focused ? 2 : 1; + if (bw >= 2) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, bcol); + else meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, + r > 1 ? r - 1 : 0, bcol); + int has = w->buf && w->buf[0]; + uint32_t col = has ? th->onSurface : th->onSurfaceVariant; + const char *s = has ? w->buf : (w->placeholder[0] ? w->placeholder : ""); + int tx = w->x + 12; + int ty = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2; + meow_ui_text(tx, ty, s, th->font_body, col); + if (focused && has) { + int tw = meow_ui_text_width(w->buf, th->font_body); + meow_ui_fill_rect(tx + tw, ty, 2, meow_ui_text_height(th->font_body), th->primary); + } + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } + case W_CARD: { + int r = widget_radius(w, th->radius_card); + uint32_t base = widget_bg(w, th, th->surfaceContainerLow); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, base); + if (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) + meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, w->opts.stroke); + if (w->state & MEOW_WS_SELECTED) + meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 24); + if (w->nchildren > 0 && w->children[0]) meow_widget_draw(w->children[0]); + if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } + case W_DIVIDER: { + uint32_t c = widget_bg(w, th, th->outlineVariant); + if (w->variant) meow_ui_fill_rect(w->x, w->y, 1, w->h, c); + else meow_ui_fill_rect(w->x, w->y, w->w, 1, c); + break; + } + case W_BADGE: { + int count = w->value; + if (count < 0) break; /* 隐藏 */ + uint32_t bgc = th->error; /* MD3 角标用 error */ + if (count == 0) { + meow_gfx_fill_circle(w->x, w->y, 5, bgc); + } else { + char s[16]; + snprintf(s, sizeof s, "%d", count); + int tw = meow_ui_text_width(s, th->font_body); + int pw = tw + 12; if (pw < 18) pw = 18; + meow_gfx_fill_round(w->x - pw / 2, w->y - 11, pw, 22, 11, bgc); + meow_ui_text_center(w->x, w->y - meow_ui_text_height(th->font_body) / 2, s, th->font_body, th->onError); + } + break; + } + case W_CHECKBOX: { + int sel = (w->state & MEOW_WS_SELECTED) != 0; + int sz = w->w; + int r = widget_radius(w, 4); + if (sel) { + meow_gfx_fill_round(w->x, w->y, sz, sz, r, th->primary); + int ax = w->x + 9, ay1 = w->y + 17, ay2 = w->y + 23, bx = w->x + 15, cx = w->x + 25, cy = w->y + 11; + meow_gfx_line(ax, ay1, bx, ay2, 3, th->onPrimary); + meow_gfx_line(bx, ay2, cx, cy, 3, th->onPrimary); + } else { + meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline); + } + if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, sz, sz, th->onSurface, 90); + break; + } + case W_RADIO: { + int sel = (w->state & MEOW_WS_SELECTED) != 0; + int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2; + meow_gfx_ring_arc(ccx, ccy, 12, 2, 0, 360, sel ? th->primary : th->outline); + if (sel) meow_gfx_fill_circle(ccx, ccy, 6, th->primary); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } + case W_PROGRESS: { + if (w->variant == 1) { /* 圆形 */ + int ccx = w->x, ccy = w->y, rr = w->w; + int thk = 4; + meow_gfx_ring_arc(ccx, ccy, rr, thk, 0, 360, th->surfaceVariant); + if (w->value < 0) { /* 不定:按时间推进一段弧(App 在 on_tick 里重绘驱动) */ + int start = (int)((meow_ui_time_ms() / 30) % 360); + meow_gfx_ring_arc(ccx, ccy, rr, thk, start - 90, 90, th->primary); + } else if (w->value > 0) { + int sweep = w->value * 360 / 100; + if (sweep > 0) meow_gfx_ring_arc(ccx, ccy, rr, thk, -90, sweep, th->primary); + } + } else { /* 线性 */ + int thk = w->h > 0 ? w->h : 4; + int yy = w->y + w->h / 2 - thk / 2; + meow_ui_fill_rect(w->x, yy, w->w, thk, th->surfaceVariant); + if (w->value < 0) { /* 不定:移动块 */ + int fw = w->w / 3; + int pos = (int)((meow_ui_time_ms() / 12) % (w->w + fw)) - fw; + meow_ui_fill_rect(w->x + pos, yy, fw, thk, th->primary); + } else if (w->value > 0) { + int fw = w->value * w->w / 100; + if (fw > 0) meow_ui_fill_rect(w->x, yy, fw, thk, th->primary); + } + } + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } + case W_FAB: { + int r = widget_radius(w, w->w / 2); + uint32_t base = widget_bg(w, th, th->primaryContainer); + if (w->state & MEOW_WS_PRESSED) base = state_tint(base, th, 12); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, base); + uint32_t tc = widget_fg(w, th, th->onPrimaryContainer); + meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, + w->label, th->font_body, tc); + if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } + case W_TOPAPPBAR: { + meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surfaceContainerHigh); + int mid = w->y + w->h / 2; + int tx = w->x + 16; + int ty = mid - meow_ui_text_height(th->font_title) / 2; + if (w->back_on) { + int bx = w->x + 22, by = mid; + meow_gfx_line(bx + 4, by - 7, bx - 4, by, 2, th->onSurface); + meow_gfx_line(bx - 4, by, bx + 4, by + 7, 2, th->onSurface); + tx = w->x + 48; + } + if (w->label[0]) meow_ui_text(tx, ty, w->label, th->font_title, th->onSurface); + int ay = mid - meow_ui_text_height(th->font_body) / 2; + int ax = w->x + w->w - 12; + for (int i = w->nitems - 1; i >= 0; i--) { + int aw = meow_ui_text_width(w->items[i], th->font_body) + 12; + ax -= aw; + meow_ui_text_center(ax + aw / 2, ay, w->items[i], th->font_body, th->onSurface); + } + meow_ui_fill_rect(w->x, w->y + w->h - 1, w->w, 1, th->outlineVariant); + break; + } + case W_NAVBAR: { + meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surfaceContainer); + if (w->nitems <= 0) break; + int iw = w->w / w->nitems; + int ay = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2 + 12; + for (int i = 0; i < w->nitems; i++) { + int ix = w->x + i * iw; + int sel = (i == w->selected); + if (sel) meow_gfx_fill_round(ix + iw / 2 - 24, w->y + 8, 48, 22, 11, th->secondaryContainer); + uint32_t col = sel ? th->onSecondaryContainer : th->onSurfaceVariant; + meow_ui_text_center(ix + iw / 2, ay, w->items[i], th->font_body, col); + } + meow_ui_fill_rect(w->x, w->y, w->w, 1, th->outlineVariant); + break; + } + case W_TABBAR: { + meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surface); + if (w->nitems <= 0) break; + int iw = w->w / w->nitems; + int ay = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2; + for (int i = 0; i < w->nitems; i++) { + int ix = w->x + i * iw; + int sel = (i == w->selected); + meow_ui_text_center(ix + iw / 2, ay, w->items[i], th->font_body, + sel ? th->primary : th->onSurfaceVariant); + if (sel) meow_ui_fill_rect(ix, w->y + w->h - 2, iw, 2, th->primary); + } + meow_ui_fill_rect(w->x, w->y + w->h - 1, w->w, 1, th->outlineVariant); + break; + } case W_SPACER: break; /* 不可见空白 */ default: diff --git a/test_host_stub.c b/test_host_stub.c new file mode 100644 index 0000000..da41ebf --- /dev/null +++ b/test_host_stub.c @@ -0,0 +1,24 @@ +/* test_host_stub.c — 宿主逻辑测试打桩:meow_font_* / meow_ui_global_render。 + * 供 test_sb_collapse.c 链真实 meow_ui_gfx.o/meow_ui_sys.o 时提供(无 /dev/fb0、无字库的宿主环境)。 + * 仅为宿主单测编译;不进 meow_ui.a(build_sdk.sh 的 OBJS 不含本文件)。 */ +#include "meow_ui.h" +#include "meow_font.h" /* 原型(meow_ui_gfx.c include 的同款头) */ +#include + +void meow_font_set_fb(unsigned w, unsigned h, unsigned pitch) { (void)w;(void)h;(void)pitch; } +int meow_font_load(const char *p, const char *i) { (void)p;(void)i; return -1; } /* 无库:g_mfont_ready=0 → 5x7 回退 */ +void meow_font_unload(void) {} +int meow_font_reload_tier(int t, const char *p, const char *i) { (void)t;(void)p;(void)i; return -1; } +void meow_font_drawtext(uint16_t *fb, int x, int y, const char *s, uint16_t c, int size) { (void)fb;(void)x;(void)y;(void)s;(void)c;(void)size; } +int meow_font_width_text(const char *s, int size) { (void)size; return (int)strlen(s) * 6; } +int meow_font_line_height(int size) { (void)size; return 20; } +int meow_font_config_multifont(int on, const char *gp, const char *gi, + const meow_font_class_cfg cfg[MEOW_FCLS_COUNT], + int *distinct, int *truncated) +{ (void)on;(void)gp;(void)gi;(void)cfg;(void)distinct;(void)truncated; return -1; } + +/* present() 的 App 跨 Activity 全局渲染骨架(宿主无控件,no-op) */ +void meow_ui_global_render(void) {} + +/* meow_ui_run 触摸分流调用 meow_ui_global_hit(全局控件命中;宿主无全局控件 → 不消费) */ +int meow_ui_global_hit(int x, int y) { (void)x; (void)y; return 0; } diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c new file mode 100644 index 0000000..168ef5b --- /dev/null +++ b/test_meow_ui_widgets.c @@ -0,0 +1,180 @@ +/* test_meow_ui_widgets.c — 宿主验证 §5.8.6 Material 子集纯逻辑(create/hit/state/滚动/选中)+ + * 各组件绘制(5x7 字库兜底)dump PPM 目检。不依赖 /dev/fb0/字库(meow_font 打桩)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + * 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕) + */ +#include "meow_ui.h" +#include +#include +#include +#include + +/* 测试数据源(列表容器) */ +static int list_src(int idx, char *label, int len, void *ctx) +{ + (void)ctx; + if (idx < 0 || idx >= 100) return 0; + snprintf(label, (size_t)len, "item %d", idx); + return 1; +} + +static meow_widget *g_w[64]; +static int g_wn = 0; +static meow_widget *W(meow_widget *w) { g_w[g_wn++] = w; return w; } + +static void free_all(void) +{ + for (int i = 0; i < g_wn; i++) meow_widget_free(g_w[i]); + g_wn = 0; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + int xres = meow_ui_width(), yres = meow_ui_height(); + assert(xres == 480 && yres == 720); + + /* ---------- Button 五型:create_variant/set_variant + 命中 ---------- */ + meow_widget *bf = W(meow_btn_create_variant(10, 10, 200, 56, "FILLED", MEOW_BTN_FILLED)); + meow_widget *bt = W(meow_btn_create_variant(10, 80, 200, 56, "TONAL", MEOW_BTN_TONAL)); + meow_widget *bo = W(meow_btn_create_variant(10, 150, 200, 56, "OUTLINED", MEOW_BTN_OUTLINED)); + meow_widget *bx = W(meow_btn_create_variant(10, 220, 200, 56, "TEXT", MEOW_BTN_TEXT)); + meow_widget *be = W(meow_btn_create_variant(10, 290, 200, 56, "ELEV", MEOW_BTN_ELEVATED)); + assert(meow_btn_set_variant(bt, MEOW_BTN_TONAL) == 0); /* 同值 */ + assert(meow_btn_set_variant(bo, MEOW_BTN_OUTLINED) == 0); + assert(meow_widget_hit(bf, 100, 30) == 1); + assert(meow_widget_hit(bf, 5, 5) == 0); /* 越界 */ + meow_widget_set_enabled(bf, 0); + assert(meow_widget_hit(bf, 100, 30) == 0); /* 禁用不命中 */ + meow_widget_set_enabled(bf, 1); + meow_widget_set_selected(be, 1); + assert(meow_widget_selected(be) == 1); + printf("PASS button-variant: create/set/hit/disabled/selected\n"); + + /* ---------- TextField ---------- */ + char tfbuf[32] = ""; + meow_widget *tf = W(meow_textfield_create(10, 360, 300, 44, "Type here...", tfbuf, 32, MEOW_IME_ASCII)); + strcpy(tfbuf, "abc"); + assert(strcmp(meow_textfield_text(tf), "abc") == 0); + assert(meow_widget_hit(tf, 20, 380) == 1); + meow_textfield_set_placeholder(tf, "placeholder"); + printf("PASS textfield: create/text/placeholder/hit\n"); + + /* ---------- Card + content ---------- */ + meow_widget *card = W(meow_card_create(240, 10, 220, 180)); + meow_widget *inner = W(meow_btn_create_variant(260, 40, 180, 44, "IN CARD", MEOW_BTN_FILLED)); + meow_card_set_content(card, inner); + assert(meow_widget_hit(card, 250, 20) == 1); + printf("PASS card: create/content/hit\n"); + + /* ---------- Divider / Badge ---------- */ + meow_widget *dv = W(meow_divider_create(10, 470, 200, 0)); + meow_widget *dv2 = W(meow_divider_create(230, 470, 8, 1)); + meow_widget *bd = W(meow_badge_create(400, 100)); + meow_badge_set(bd, 3); + meow_badge_set(bd, 0); + printf("PASS divider/badge: create/set\n"); + + /* ---------- Checkbox / Radio ---------- */ + meow_widget *ck = W(meow_checkbox_create(300, 200)); + meow_widget *rd = W(meow_radio_create(300, 250)); + meow_widget_set_selected(ck, 1); + meow_widget_set_selected(rd, 1); + assert(meow_widget_selected(ck) == 1); + assert(meow_widget_hit(rd, 315, 265) == 1); + printf("PASS checkbox/radio: create/select/hit\n"); + + /* ---------- ProgressBar ---------- */ + meow_widget *pg = W(meow_progress_create(10, 520, 300, 6, 40)); + meow_progress_set(pg, 200); /* clamp 100(无 getter,仅不崩) */ + meow_widget *pgc = W(meow_progress_circular_create(420, 520, 20, 66)); + meow_progress_set(pgc, -1); /* 不定 */ + printf("PASS progress: create/set linear+circular\n"); + + /* ---------- FAB ---------- */ + meow_widget *fab = W(meow_fab_create(410, 620, 56, "+")); + assert(meow_widget_hit(fab, 430, 640) == 1); + printf("PASS fab: create/hit\n"); + + /* ---------- TopAppBar ---------- */ + meow_widget *tab_ = W(meow_topappbar_create(0, 0, 480, 56, "Title")); + meow_topappbar_set_back(tab_, 1); + int a0 = meow_topappbar_add_action(tab_, "Save"); + int a1 = meow_topappbar_add_action(tab_, "Edit"); + assert(a0 == 0 && a1 == 1); + assert(meow_topappbar_hit(tab_, 10, 28) == MEOW_TOPBAR_BACK); /* 返回区 */ + assert(meow_topappbar_hit(tab_, 440, 28) == 1); /* 右缘动作 "Edit"(最右) */ + printf("PASS topappbar: create/back/action/hit\n"); + + /* ---------- NavigationBar ---------- */ + meow_widget *nav = W(meow_navigationbar_create(0, 680, 480, 40)); + int n0 = meow_navigationbar_add_item(nav, "Home"); + int n1 = meow_navigationbar_add_item(nav, "Library"); + int n2 = meow_navigationbar_add_item(nav, "Settings"); + assert(n0 == 0 && n1 == 1 && n2 == 2); + meow_navigationbar_set_selected(nav, 2); + assert(meow_navigationbar_selected(nav) == 2); + assert(meow_navigationbar_hit(nav, 420, 700) == 2); /* 第 3 项 */ + assert(meow_navigationbar_hit(nav, 10, 700) == 0); + printf("PASS navigationbar: add/select/hit\n"); + + /* ---------- TabBar ---------- */ + meow_widget *tbb = W(meow_tabbar_create(0, 660, 480, 36)); + meow_tabbar_add_tab(tbb, "Playlist"); + meow_tabbar_add_tab(tbb, "Queue"); + meow_tabbar_set_selected(tbb, 1); + assert(meow_tabbar_hit(tbb, 300, 678) == 1); + printf("PASS tabbar: add/select/hit\n"); + + /* ---------- 列表容器(LazyColumn 虚拟化 + 滚动) ---------- */ + meow_widget *lc = W(meow_lazycolumn_create(20, 460, 260, 180, 40, 100, list_src, NULL)); + assert(meow_list_scroll(lc) == 0); + assert(meow_list_max_scroll(lc) == 100 * 40 - 180); /* 4000-180=3820 */ + meow_list_set_scroll(lc, 5000); + assert(meow_list_scroll(lc) == 3820); /* clamp 到 max */ + meow_list_scroll_by(lc, -320); + assert(meow_list_scroll(lc) == 3500); + assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */ + meow_list_set_item_h(lc, 50); + assert(meow_widget_hit(lc, 30, 470) == 1); + /* LazyRow 横向 */ + meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL)); + meow_list_scroll_by(lr, 200); + assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60)); + printf("PASS lazy list: scroll clamp/virtualize/hit_index (col+row)\n"); + + /* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */ + meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one"); + const char *items[] = { "Alpha", "Beta", "Gamma" }; + meow_dialog_set_items(dlg, items, 3, 1); + assert(meow_dialog_result(dlg) == 0); /* 未 show 默认 */ + meow_dialog_free(dlg); + meow_dialog *dc = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?"); + assert(meow_dialog_result(dc) == 0); + meow_dialog_free(dc); + meow_sheet *sh = meow_sheet_create("Options", 0); + int s0 = meow_sheet_add_item(sh, "Copy"); + int s1 = meow_sheet_add_item(sh, "Delete"); + assert(s0 == 0 && s1 == 1); + meow_sheet_free(sh); + printf("PASS dialog/sheet: create/set_items/add_item (blocking show skipped)\n"); + + /* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + for (int i = 0; i < g_wn; i++) meow_widget_draw(g_w[i]); + meow_ui_flush(); + printf("PASS draw: all %d widgets rendered, screen dumps to /tmp/b2.ppm\n", g_wn); + + free_all(); + meow_ui_shutdown(); + printf("ALL PASS\n"); + return 0; +} From 257ff7608eecaf6ce4a7f181a36e984f1432b874 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 23:01:31 +0800 Subject: [PATCH 17/49] =?UTF-8?q?feat(sdk):=20meow=5Fconfig=5F*=20App?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=AF=BB=E5=86=99API(agent-158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 6 API: meow_config_dir/set/get/int/bool/remove; 落 TF MeowData//data/.cfg(env MEOW_DATA_DIR, 回退/tmp/meowcfg_) - 与系统设置 meow_settings_*(板载settings.conf) 区分; key清洗[A-Za-z0-9._-]→_ 防路径穿越; 值上限4096; 降级不崩(pthread_once/mutex) - MEOW_CFG_* 错误码(避开 meow_ipc.h 枚举命名空间, 防宏展开冲突); 数值与 MEOWOS_API/meow_settings.h 同值 - meow_ui.a=92e61fb8(新增config成员) hello=5e31d919不变(0回归, 静态链未拉入新成员); build_sdk 0warning; 宿主test_meow_ui_config ALL PASS - 未重链launcher(launcher未用config); 遗留真机验证MEOW_DATA_DIR落盘/无卡降级 --- build_sdk.sh | 2 +- meow_ui.h | 35 +++++++ meow_ui_config.c | 226 ++++++++++++++++++++++++++++++++++++++++++ test_meow_ui_config.c | 126 +++++++++++++++++++++++ 4 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 meow_ui_config.c create mode 100644 test_meow_ui_config.c diff --git a/build_sdk.sh b/build_sdk.sh index cecf0c5..9994d4d 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index a64dd49..6b16ef0 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -21,6 +21,17 @@ #include #include +/* meow_config_*(§9.2)专用返回码。数值与 MEOWOS_API.md §1 的同名错误码一致, + * 但用 meow_cfg 前缀——因为 MEOW_OK/MEOW_ERR_* 这个命名空间已被共享协议头 + * meow_ipc.h(枚举)与 launcher meow_settings.h(宏)占用,SDK 的 meow_notify_ipc.c + * 同 TU include meow_ui.h + meow_ipc.h,若在此定义同名宏会导致「宏展开枚举标识符」冲突。 */ +#define MEOW_CFG_OK 0 /* 成功 */ +#define MEOW_CFG_ERR_INVAL -1 /* 参数/取值非法(空 key、NULL out、outsz<=0 等) */ +#define MEOW_CFG_ERR_NOENT -2 /* 键未设/文件不存在 */ +#define MEOW_CFG_ERR_IO -5 /* 读写(存储/文件)失败 */ +#define MEOW_CFG_ERR_NOMEM -6 /* 路径超长 */ +#define MEOW_CFG_ERR_NODEV -10 /* 无数据目录(卡不在/只读/不可建) */ + #ifdef __cplusplus extern "C" { #endif @@ -611,6 +622,30 @@ 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); +/* ============================================================ */ +/* §9.2 应用配置读写(meow_config_*,M2.x 最小可用版) */ +/* ============================================================ */ +/* App 自有配置读写:与系统设置 meow_settings_*(板载 settings.conf、系统级、带 schema) + * 明确区分——这是单个 App 自己的键值偏好,落 TF 数据目录,不占板载、无 schema。 + * + * - 数据目录:优先 env MEOW_DATA_DIR(launcher 注入 /usr/data/mnt/sd_0/MeowData//data, + * 见 app_launcher.c launch_app);无 env(手动跑/宿主测/无卡)回退 /tmp/meowcfg_ + * (非持久但仍可写)。meow_config_dir() 返回最终目录;空/不可建返回 NULL。 + * - 文件布局:每键一小文件 /.cfg,内容=值字符串(空串=0 字节文件)。 + * key 只允许 [A-Za-z0-9._-],其余字符替换为 '_'(杜绝路径穿越);key 上限 64。 + * - 轻量:每次 get 读文件(App 配置小),不做大内存缓存;pthread_once 惰性初始化目录。 + * - 失败降级:无数据目录/写失败 → 返回负错误码(见上 MEOW_CFG_ERR_*),不崩不阻塞。 + * + * 返回码:MEOW_CFG_OK(0) 成功;(set) 负=失败;(get) MEOW_CFG_ERR_NOENT(-2)=键未设/文件不存在; + * meow_config_int/bool 对任何非 MEOW_CFG_OK 一律返回 def。 + * 错误码数值同 MEOWOS_API.md §1 / meow_settings_*(MEOW_ERR_*)一致,便于 App 习惯。 */ +const char *meow_config_dir(void); /* 当前 App 数据目录(data);空/不可建 NULL */ +int meow_config_set(const char *key, const char *val);/* 写 /.cfg;成功 MEOW_CFG_OK(0) */ +int meow_config_get(const char *key, char *out, int outsz); /* 读;未设/不存在返回 MEOW_CFG_ERR_NOENT */ +int meow_config_int(const char *key, int def); /* 便捷 int;未设/非法返回 def */ +int meow_config_bool(const char *key, int def); /* 便捷 bool;未设/非法返回 def */ +void meow_config_remove(const char *key); /* 删键(无该键/无目录则静默) */ + /* ============================================================ */ /* §5.7 应用可读 TF 卡字体列表(M2.58:字体索引 / 查列表 / 丢失降级默认) */ /* ============================================================ */ diff --git a/meow_ui_config.c b/meow_ui_config.c new file mode 100644 index 0000000..ce99fa7 --- /dev/null +++ b/meow_ui_config.c @@ -0,0 +1,226 @@ +/* + * meow_ui_config.c — MeowOS 应用配置读写 API(M2.x,§9.2)。 + * + * 背景:第三/系统 App 需要读写「自己的配置」(播放列表偏好、UI 选项、最近列表等)。 + * 此前 SDK 无统一接口,App 只能自己造轮子(自读 env + snprintf + fopen/fprintf), + * 重复、易错、无失败降级。本文件实现 SDK 统一 meow_config_*: + * + * - app 一行 meow_config_set/get 读写键值,SDK 管「数据目录解析 / 文件布局 / 降级」; + * - 数据目录:优先 env MEOW_DATA_DIR(launcher 注入 /usr/data/mnt/sd_0/MeowData//data, + * 见 app_launcher.c launch_app,exFAT 无 symlink → env 传址);无 env(手动跑/宿主测/无卡) + * 回退 /tmp/meowcfg_(非持久但仍可写); + * - 文件布局:每键一小文件 /.cfg,内容=值字符串(空串=0 字节文件); + * key 只允许 [A-Za-z0-9._-],其余字符(含 '/')替换为 '_',杜绝路径穿越/子目录注入; + * - 轻量:每次 get 读文件(App 配置小),不做大内存缓存;pthread_once 惰性初始化目录; + * - 线程安全:pthread_mutex 串行库内单键操作(不同 key 独立文件,无共享读写竞态); + * - 失败降级:无数据目录/建目录失败/读写失败 → 返回负错误码(MEOW_CFG_ERR_*),不崩不阻塞。 + * + * 与 meow_settings_*(系统设置)的界限:settings 是「系统/MeowOS 设置」(板载 + * /usr/data/meow/settings.conf、带 schema/取值域/枚举/listener),config 是「App 自有 KV」 + * (TF 数据目录、raw 字符串、无 schema)。二者命名/落点/语义不同层;本文件**不 import** + * meow_settings,纯 libc 完成,可宿主 gcc 直接编译单测。 + * + * 约定:与 meow_ui_log.c 同风格(M2.x 中文注释 + pthread_once 惰性 init);纯 libc,零依赖。 + */ +#include "meow_ui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 内部常量(轻量键值小文件,见头注释) */ +#define MEOW_CFG_DIR_MAX 320 /* 数据目录绝对路径上限 */ +#define MEOW_CFG_KEY_MAX 64 /* key 上限(清洗后) */ +#define MEOW_CFG_VAL_MAX 4096 /* 值上限(set 截断;get 按 outsz-1 读) */ +#define MEOW_CFG_PATH_MAX (MEOW_CFG_DIR_MAX + MEOW_CFG_KEY_MAX + 8) /* /.cfg + NUL */ + +/* 内部状态(各字段约定见头注释) */ +static pthread_mutex_t g_cfg_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t g_cfg_once = PTHREAD_ONCE_INIT; + +static char g_cfg_dir[MEOW_CFG_DIR_MAX] = ""; /* 解析后的数据目录(env 或 /tmp 回退) */ +static int g_cfg_ready = 0; /* 1=目录已解析且 mkdir 成功 */ +static int g_cfg_env = 0; /* 1=来自 MEOW_DATA_DIR(持久),0=/tmp 回退 */ + +/* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 + * 仿 meow_ui_log.c log_mkdir_p;用于数据目录不存在时补齐(语义幂等)。 */ +static int cfg_mkdir_p(const char *path) +{ + char tmp[MEOW_CFG_DIR_MAX]; + size_t len, i; + if (!path || !path[0] || strlen(path) >= sizeof tmp) return -1; + len = strlen(path); + memcpy(tmp, path, len + 1); + if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; /* 去尾斜杠 */ + for (i = 1; i <= strlen(tmp); i++) { + if (tmp[i] == '/' || tmp[i] == '\0') { + char ch = tmp[i]; + tmp[i] = '\0'; + if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1; + tmp[i] = ch; + } + } + return 0; +} + +/* 一次性初始化(pthread_once 保证仅一次):解析数据目录 + mkdir -p + 置 ready。 */ +static void cfg_init(void) +{ + const char *env = getenv("MEOW_DATA_DIR"); + + /* 层序:1) env MEOW_DATA_DIR(launcher 注入,持久);2) 回退 /tmp/meowcfg_。 */ + if (env && env[0]) { + snprintf(g_cfg_dir, sizeof g_cfg_dir, "%s", env); + g_cfg_env = 1; + } else { + snprintf(g_cfg_dir, sizeof g_cfg_dir, "/tmp/meowcfg_%d", (int)getpid()); + g_cfg_env = 0; + } + + /* 去尾斜杠(统一 /.cfg 拼接) */ + size_t n = strlen(g_cfg_dir); + while (n > 1 && g_cfg_dir[n - 1] == '/') g_cfg_dir[--n] = '\0'; + + /* mkdir 幂等;失败只降级(g_cfg_ready=0 → meow_config_dir 返回 NULL),不崩。 + * 注:env 指向 /data 时 launcher 已建该目录;卡不在/只读 → 此处失败降级。 */ + if (cfg_mkdir_p(g_cfg_dir) == 0) g_cfg_ready = 1; +} + +/* key 清洗:只保留 [A-Za-z0-9._-],其余(含 '/'、空白、'..')逐字符替换为 '_'。 + * 返回 0=成功(写入 out),-1=空 key/全非法。清洗后无路径分隔,杜绝路径穿越。 */ +static int cfg_sanitize_key(const char *key, char *out, int n) +{ + int len = 0; + const unsigned char *p; + if (!key || n <= 0) return -1; + for (p = (const unsigned char *)key; *p && len < n - 1; p++) { + unsigned char c = *p; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-') + out[len++] = (char)c; + else + out[len++] = '_'; + } + if (len == 0) return -1; /* 全非法(如纯 '/')→ 拒绝 */ + out[len] = '\0'; + return 0; +} + +/* 拼 /.cfg 到 path。返回 0=成功,-1=路径超长。 */ +static int cfg_path(const char *key, char *path, int n) +{ + const char *d = meow_config_dir(); + if (!d) return -1; + if (snprintf(path, (size_t)n, "%s/%s.cfg", d, key) >= n) return -1; + return 0; +} + +const char *meow_config_dir(void) +{ + /* 不取 g_cfg_mu:pthread_once 保证 cfg_init 只跑一次,且其完成后 g_cfg_dir/g_cfg_ready + * 已是稳定快照(once 自带 happens-before/内存序)。set/get/remove 持锁后经 cfg_path 也调 + * 本函数——若在此加锁会自我死锁(非递归互斥)。故仅返回只读快照。 */ + pthread_once(&g_cfg_once, cfg_init); + return g_cfg_ready ? g_cfg_dir : NULL; +} + +int meow_config_set(const char *key, const char *val) +{ + char k[MEOW_CFG_KEY_MAX + 1]; + char path[MEOW_CFG_PATH_MAX]; + const char *v = val ? val : ""; /* NULL 视为空串(=清空该键) */ + size_t vlen, towrite; + int fd, rc = MEOW_CFG_OK, s; + + if (cfg_sanitize_key(key, k, sizeof k) != 0) return MEOW_CFG_ERR_INVAL; + vlen = strlen(v); + towrite = vlen > MEOW_CFG_VAL_MAX ? MEOW_CFG_VAL_MAX : vlen; /* 值上限截断 */ + + pthread_mutex_lock(&g_cfg_mu); + s = cfg_path(k, path, sizeof path); + if (s != 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOMEM; } + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + if (!g_cfg_env) fprintf(stderr, "meow_config: set '%s' open %s: %s\n", k, path, strerror(errno)); + pthread_mutex_unlock(&g_cfg_mu); + return MEOW_CFG_ERR_IO; + } + /* 循环写完(防短写;配置小,实际单次 write 即完) */ + size_t off = 0; + while (off < towrite) { + ssize_t w = write(fd, v + off, towrite - off); + if (w < 0) { rc = MEOW_CFG_ERR_IO; break; } + if (w == 0) { rc = MEOW_CFG_ERR_IO; break; } + off += (size_t)w; + } + close(fd); + pthread_mutex_unlock(&g_cfg_mu); + return rc; +} + +int meow_config_get(const char *key, char *out, int outsz) +{ + char k[MEOW_CFG_KEY_MAX + 1]; + char path[MEOW_CFG_PATH_MAX]; + ssize_t n; + + if (!out || outsz <= 0) return MEOW_CFG_ERR_INVAL; + out[0] = '\0'; + if (cfg_sanitize_key(key, k, sizeof k) != 0) return MEOW_CFG_ERR_INVAL; + + pthread_mutex_lock(&g_cfg_mu); + if (cfg_path(k, path, sizeof path) != 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOMEM; } + + int fd = open(path, O_RDONLY); + if (fd < 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOENT; } /* 未设/不存在 */ + + n = read(fd, out, (size_t)outsz - 1); + if (n < 0) n = 0; + close(fd); + pthread_mutex_unlock(&g_cfg_mu); + + out[n] = '\0'; /* 截断/短读均安全 NUL 终止 */ + return MEOW_CFG_OK; +} + +int meow_config_int(const char *key, int def) +{ + char buf[32]; + char *end = NULL; + long v; + if (meow_config_get(key, buf, sizeof buf) != MEOW_CFG_OK) return def; + v = strtol(buf, &end, 10); + if (end == buf) return def; /* 非数字 → def */ + return (int)v; +} + +int meow_config_bool(const char *key, int def) +{ + char buf[16]; + if (meow_config_get(key, buf, sizeof buf) != MEOW_CFG_OK) return def; + if (!strcmp(buf, "1") || !strcmp(buf, "true") || !strcmp(buf, "yes") || !strcmp(buf, "on")) + return 1; + if (!strcmp(buf, "0") || !strcmp(buf, "false") || !strcmp(buf, "no") || !strcmp(buf, "off")) + return 0; + return def; +} + +void meow_config_remove(const char *key) +{ + char k[MEOW_CFG_KEY_MAX + 1]; + char path[MEOW_CFG_PATH_MAX]; + + if (cfg_sanitize_key(key, k, sizeof k) != 0) return; /* 非法 key 静默 */ + + pthread_mutex_lock(&g_cfg_mu); + if (cfg_path(k, path, sizeof path) == 0) + unlink(path); /* 无目录 / 文件不存在:unlink 失败即静默(void 语义) */ + pthread_mutex_unlock(&g_cfg_mu); +} diff --git a/test_meow_ui_config.c b/test_meow_ui_config.c new file mode 100644 index 0000000..ff6b733 --- /dev/null +++ b/test_meow_ui_config.c @@ -0,0 +1,126 @@ +/* + * test_meow_ui_config.c — meow_config_* 宿主单测(2026-08-30)。 + * + * 编译(宿主,config 为纯 libc,无需 SDK 其它对象): + * gcc -Wall -Wextra test_meow_ui_config.c meow_ui_config.c -o test_meow_ui_config -lpthread + * + * 两种模式(meow_config_dir 经 pthread_once 缓存,目录只在首次调用确定,故分两次跑): + * ./test_meow_ui_config env → 设 MEOW_DATA_DIR=,验证持久目录读写 + * ./test_meow_ui_config tmp → unsetenv MEOW_DATA_DIR,验证 /tmp/meowcfg_ 回退 + * + * 任一断言失败 → 打印 FAIL 并以非零退出码结束。 + */ +#include "meow_ui.h" + +#include +#include +#include +#include + +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) + +int main(int argc, char **argv) +{ + const char *mode = (argc > 1) ? argv[1] : "env"; + char *tmpdir = NULL; + + if (strcmp(mode, "tmp") == 0) { + unsetenv("MEOW_DATA_DIR"); + } else { + tmpdir = strdup("/tmp/meowcfgtestXXXXXX"); + if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; } + setenv("MEOW_DATA_DIR", tmpdir, 1); + } + + printf("== meow_config test [%s] ==\n", mode); + + const char *dir = meow_config_dir(); + CHECK(dir != NULL, "meow_config_dir() 非 NULL"); + printf(" dir = %s\n", dir ? dir : "(null)"); + if (strcmp(mode, "tmp") == 0) + CHECK(dir && strstr(dir, "/tmp/meowcfg_") != NULL, "tmp 回退目录为 /tmp/meowcfg_"); + else + CHECK(dir && strcmp(dir, tmpdir) == 0, "env 目录 = MEOW_DATA_DIR"); + + /* --- set/get 往返 --- */ + CHECK(meow_config_set("k1", "hello") == MEOW_CFG_OK, "set k1=hello -> MEOW_CFG_OK"); + char buf[128]; + CHECK(meow_config_get("k1", buf, sizeof buf) == MEOW_CFG_OK, "get k1 -> MEOW_CFG_OK"); + CHECK(strcmp(buf, "hello") == 0, "get k1 值 = hello"); + + /* --- 全数字 int / bool 解析 --- */ + meow_config_set("num", "42"); + CHECK(meow_config_int("num", -1) == 42, "int(num)=42"); + meow_config_set("truth", "true"); + CHECK(meow_config_bool("truth", 0) == 1, "bool(true)=1"); + meow_config_set("lie", "off"); + CHECK(meow_config_bool("lie", 1) == 0, "bool(off)=0"); + CHECK(meow_config_int("nonnum", 7) == 7, "int 非数字 -> def"); + CHECK(meow_config_bool("nonbool", 3) == 3, "bool 非法 -> def"); + + /* --- 未设键 --- */ + CHECK(meow_config_get("absent", buf, sizeof buf) == MEOW_CFG_ERR_NOENT, "get 未设键 -> MEOW_CFG_ERR_NOENT"); + CHECK(meow_config_int("absent", 9) == 9, "int 未设键 -> def"); + CHECK(meow_config_bool("absent", 1) == 1, "bool 未设键 -> def"); + + /* --- int 便捷写(经 set)再读 --- */ + char nbuf[16]; + meow_config_get("num", nbuf, sizeof nbuf); + CHECK(strcmp(nbuf, "42") == 0, "int 存储为十进制 '42'"); + + /* --- 空值(NULL=清空为 0 字节文件,与「未设」区分)--- */ + meow_config_set("empty", ""); + CHECK(meow_config_get("empty", buf, sizeof buf) == MEOW_CFG_OK, "get 空值键 -> MEOW_CFG_OK"); + CHECK(buf[0] == '\0', "空值键读回空串"); + CHECK(meow_config_get("empty", buf, sizeof buf) != MEOW_CFG_ERR_NOENT, "空值键 ≠ 未设"); + + /* --- remove --- */ + meow_config_remove("k1"); + CHECK(meow_config_get("k1", buf, sizeof buf) == MEOW_CFG_ERR_NOENT, "remove 后 get -> NOENT"); + + /* --- key 清洗:'/' 等非法字符替换为 '_',杜绝路径穿越 --- */ + meow_config_set("a/b", "slash"); + CHECK(meow_config_get("a_b", buf, sizeof buf) == MEOW_CFG_OK && strcmp(buf, "slash") == 0, + "key 'a/b' 清洗为 'a_b'"); + /* 路径穿越尝试:key 含 '/' 的非法串,不得逃出数据目录('/'→'_','.' 保留为字面量)。 */ + meow_config_set("../../etc/passwd", "evil"); + CHECK(meow_config_get(".._.._etc_passwd", buf, sizeof buf) == MEOW_CFG_OK && + strcmp(buf, "evil") == 0, "key '../../etc/passwd' 清洗为 '.._.._etc_passwd'('/'→'_',无穿越)"); + + /* --- 参数校验 --- */ + CHECK(meow_config_get(NULL, buf, sizeof buf) == MEOW_CFG_ERR_INVAL, "get(NULL) -> INVAL"); + CHECK(meow_config_get("x", NULL, 10) == MEOW_CFG_ERR_INVAL, "get(out=NULL) -> INVAL"); + CHECK(meow_config_get("x", buf, 0) == MEOW_CFG_ERR_INVAL, "get(outsz=0) -> INVAL"); + CHECK(meow_config_set("", "v") == MEOW_CFG_ERR_INVAL, "set(空 key) -> INVAL"); + + /* --- 多键独立 --- */ + meow_config_set("ind.a", "1"); + meow_config_set("ind.b", "2"); + meow_config_get("ind.a", buf, sizeof buf); + CHECK(strcmp(buf, "1") == 0, "多键独立 a"); + meow_config_get("ind.b", buf, sizeof buf); + CHECK(strcmp(buf, "2") == 0, "多键独立 b"); + meow_config_remove("ind.a"); + meow_config_get("ind.b", buf, sizeof buf); + CHECK(strcmp(buf, "2") == 0, "删除 a 不影响 b"); + + /* --- 值截断(>MEOW_CFG_VAL_MAX 4096 截断;读合法)--- */ + { + char big[4096 * 2]; + char rbuf[4100]; + memset(big, 'A', sizeof big - 1); + big[sizeof big - 1] = '\0'; + meow_config_set("big", big); + meow_config_get("big", rbuf, sizeof rbuf); + CHECK(strlen(rbuf) == 4096, "值 >4096 截断为 4096"); + } + + if (tmpdir) free(tmpdir); + if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; } + printf("\nRESULT: ALL PASS\n"); + return 0; +} From 3091f30c5ac2456154874037641a78d18872c68b Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 00:19:40 +0800 Subject: [PATCH 18/49] =?UTF-8?q?fix(sdk/hello):=20M2.71=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=E7=9C=9F=E6=9C=BAbug=E6=89=B9=E6=AC=A1=20=E2=80=94=20=E5=9C=86?= =?UTF-8?q?=E5=BD=A2=E8=BF=9B=E5=BA=A6=E8=84=8F=E5=8C=BA+=E6=95=B4?= =?UTF-8?q?=E7=8E=AF=E4=BF=AE=E5=A4=8D/Slider=E6=8B=96=E5=8A=A8/Dialog?= =?UTF-8?q?=C2=B7Sheet=E8=83=8C=E6=99=AF=E5=BF=AB=E7=85=A7scrim=E4=B8=8D?= =?UTF-8?q?=E5=8F=A0=E9=BB=91/Disabled=E5=9C=86=E8=A7=92=CE=B1/=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E9=87=8D=E7=BD=AE=E6=81=AF=E5=B1=8F/=E6=BB=91?= =?UTF-8?q?=E5=8A=A8=E8=B7=9F=E6=89=8B/IME=E6=AE=8B=E5=BD=B1=C2=B7?= =?UTF-8?q?=E9=94=AE=E4=BD=8D=C2=B7=E4=B8=BB=E9=A2=98=E9=85=8D=E8=89=B2/Li?= =?UTF-8?q?st=E9=80=89=E4=B8=AD=E7=82=B9=E5=87=BB=20(agent-162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui.h | 12 ++++ meow_ui_dialog.c | 105 +++++++++++++++++++++------ meow_ui_gfx.c | 26 ++++++- meow_ui_ime.c | 65 +++++++++++++---- meow_ui_input.c | 45 +++++++++++- meow_ui_internal.h | 13 ++++ meow_ui_widget.c | 71 ++++++++++++++---- test_meow_ui_widgets.c | 9 ++- test_sdk_fixes.c | 158 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 449 insertions(+), 55 deletions(-) create mode 100644 test_sdk_fixes.c diff --git a/meow_ui.h b/meow_ui.h index 6b16ef0..5813388 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -158,6 +158,11 @@ typedef struct meow_ui_events { void (*on_swipe)(int dir, int dx, int dy); /* 左滑=返回(左缘起点+抬手触发) */ void (*on_tick)(void); /* 空闲周期回调(动画/时钟) */ void (*on_swipe_progress)(int x_start, int dx);/* 左缘返回手势过程实时回调;x_start=按下X, dx=当前X-按下X */ + /* M2.71-bug2/6:拖动过程回调(跟手)。on_touch_down=按下后首个有效坐标(起点); + * on_touch_move=拖动中坐标变化(坐标变了才回调)。用于 slider 拖动 / 列表/页面滚动跟手。 + * 均在 IME 弹出或模态阻塞时被吞(键盘/模态优先喂)。可 NULL。 */ + void (*on_touch_down)(int x, int y); + void (*on_touch_move)(int x, int y); } meow_ui_events; void meow_ui_run(const meow_ui_events *ev); /* 阻塞:事件循环,直到 meow_ui_request_exit */ @@ -500,6 +505,13 @@ int meow_list_max_scroll(meow_widget *w); /* max(0, count*item - void meow_list_scroll_by(meow_widget *w, int dy); /* 相对滚动(App on_swipe 用) */ int meow_list_set_item_h(meow_widget *w, int item); /* 改行高/列宽 */ int meow_list_hit_index(meow_widget *w, int x, int y); /* 命中项下标(含滚动换算);-1=未命中项/越界 */ +/* M2.71-bug8:列表行选中 + 行点击回调。选中行已有高亮(现有绘制读 w->sel)。 + * meow_list_tap_at:命中一行则置选中并回调 tap_cb(若设),返回该行 idx;未命中返回 -1。 */ +void meow_list_set_selected(meow_widget *w, int idx); /* 选中行(越界 clamp);标脏 */ +int meow_list_selected(meow_widget *w); +typedef void (*meow_list_tap_fn)(int idx, void *ctx); +void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx); /* 命中行→回调 */ +int meow_list_tap_at(meow_widget *w, int x, int y); /* 命中行:置选中+回调,返回 idx/-1 */ /* ============================================================ */ /* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet diff --git a/meow_ui_dialog.c b/meow_ui_dialog.c index 3facc68..bca80c6 100644 --- a/meow_ui_dialog.c +++ b/meow_ui_dialog.c @@ -20,6 +20,39 @@ #define MEOW_MODAL_ROW 48 #define MEOW_MODAL_BTN 44 +/* M2.71-bug3:模态(Dialog/BottomSheet)背景快照/恢复。 + * 旧实现直接往 back_buf 画 scrim+卡片并 flush:①每次 open 再现叠一层 α150 遮罩(叠加变黑); + * ②关闭后 scrim+卡片像素留在屏上(残影);③要整屏刷新才盖掉。此处 open 前把整帧背景拍下, + * 关闭后回填 + 整帧上屏 → scrim 每次只叠一层、关闭即还原、无残留。 */ +static unsigned char *g_modal_cap = NULL; +static int g_modal_cap_line = 0, g_modal_cap_h = 0, g_modal_cap_bpp = 0; + +static void modal_capture(void) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(); + int h = meow_ui_height(); + int bpp = meow_ui_back_buf_bpp(); + if (!bb || line <= 0 || h <= 0) { g_modal_cap = NULL; return; } + size_t n = (size_t)line * (size_t)h; + if (!g_modal_cap || g_modal_cap_line != line || g_modal_cap_h != h || g_modal_cap_bpp != bpp) { + free(g_modal_cap); + g_modal_cap = (unsigned char *)malloc(n); + g_modal_cap_line = line; g_modal_cap_h = h; g_modal_cap_bpp = bpp; + } + if (g_modal_cap) memcpy(g_modal_cap, bb, n); +} + +static void modal_restore(void) +{ + unsigned char *bb = meow_ui_back_buf(); + if (!g_modal_cap || !bb) return; + size_t n = (size_t)g_modal_cap_line * (size_t)g_modal_cap_h; + memcpy(bb, g_modal_cap, n); + meow_gfx_mark_full(); + meow_ui_flush(); +} + /* ---- Dialog ---- */ struct meow_dialog { meow_dlg_t type; @@ -62,58 +95,73 @@ static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g) g->btn_w = (g->dw - 2 * pad - 12) / 2; } -static void dlg_paint(meow_dialog *d, const meow_theme *th) +/* 只画卡片内容(不含遮罩)——供「仅高亮变化」时重绘卡片区,不会把遮罩再叠一层(防叠加变黑)。 */ +static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g) { - int W = meow_ui_width(), H = meow_ui_height(); - meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); /* 遮罩压暗 */ - dlg_geom g; - dlg_calc(d, th, &g); int pad = 24; - meow_gfx_fill_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->surfaceContainerHighest); - meow_ui_draw_rect_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->outline); - meow_ui_text(g.dx + pad, g.dy + pad, d->title, th->font_title, th->onSurface); + meow_gfx_fill_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->surfaceContainerHighest); + meow_ui_draw_rect_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->outline); + meow_ui_text(g->dx + pad, g->dy + pad, d->title, th->font_title, th->onSurface); if (d->type == MEOW_DLG_SELECT) { for (int i = 0; i < d->nitems; i++) { - int ry = g.row_y + i * g.rowh; - if (ry + g.rowh > g.dy + g.dh - 12) break; + int ry = g->row_y + i * g->rowh; + if (ry + g->rowh > g->dy + g->dh - 12) break; if (i == d->cur) { - meow_ui_fill_rect(g.dx + 8, ry, g.dw - 16, g.rowh, th->primary); - meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + meow_ui_fill_rect(g->dx + 8, ry, g->dw - 16, g->rowh, th->primary); + meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2, d->items[i], th->font_body, 0xFFFFFF); } else { - meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2, d->items[i], th->font_body, th->onSurface); } - meow_ui_fill_rect(g.dx + pad, ry + g.rowh - 1, g.dw - 2 * pad, 1, th->outlineVariant); + meow_ui_fill_rect(g->dx + pad, ry + g->rowh - 1, g->dw - 2 * pad, 1, th->outlineVariant); } } else { /* INFO/CONFIRM:正文行;INPUT:文本预览 */ int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1) : (d->nitems > 0 ? d->nitems : 1); - int y = g.row_y; + int y = g->row_y; if (d->type == MEOW_DLG_INPUT) { char buf[300]; snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)"); - meow_ui_text(g.dx + pad, y, buf, th->font_body, th->onSurfaceVariant); + meow_ui_text(g->dx + pad, y, buf, th->font_body, th->onSurfaceVariant); } else { for (int i = 0; i < nmsg; i++) { if (i < d->nitems && d->items[i][0]) - meow_ui_text(g.dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant); + meow_ui_text(g->dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant); y += meow_ui_text_height(th->font_body) + 8; } } /* 取消(左,text 型)/ 确定(右,filled) */ - int bx = g.dx + pad, cx = bx + g.btn_w + 12, by = g.btn_y; - meow_ui_draw_rect_round(bx, by, g.btn_w, g.btn_h, 10, th->primary); - meow_ui_text_center(bx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y; + meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary); + meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2, "CANCEL", th->font_body, th->primary); - meow_gfx_fill_round(cx, by, g.btn_w, g.btn_h, 10, th->primary); - meow_ui_text_center(cx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary); + meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2, "OK", th->font_body, th->onPrimary); } } +/* 整帧绘制:遮罩 + 卡片。 */ +static void dlg_paint(meow_dialog *d, const meow_theme *th) +{ + meow_ui_fill_rect_alpha(0, 0, meow_ui_width(), meow_ui_height(), 0x000000, 150); /* 遮罩压暗 */ + dlg_geom g; + dlg_calc(d, th, &g); + dlg_card(d, th, &g); +} + +/* 仅重绘卡片区(遮罩不变),flush 卡片矩形:高亮变化用,不叠加遮罩。 */ +static void dlg_repaint(meow_dialog *d, const meow_theme *th) +{ + dlg_geom g; + dlg_calc(d, th, &g); + dlg_card(d, th, &g); + meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh); +} + meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title) { meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d); @@ -155,12 +203,14 @@ void meow_dialog_show(meow_dialog *d) meow_input_start(); meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ d->done = 0; d->result = 0; + modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */ dlg_paint(d, th); meow_ui_flush(); while (!d->done) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) { int x = e.a, y = e.b; dlg_geom g; @@ -168,8 +218,13 @@ void meow_dialog_show(meow_dialog *d) int in_card = x >= g.dx && x < g.dx + g.dw && y >= g.dy && y < g.dy + g.dh; if (!in_card) { d->done = 1; d->result = 0; break; } /* 遮罩外=取消 */ if (d->type == MEOW_DLG_SELECT) { + /* M2.71-bug3:点项 → 立即刷「选中」高亮(不下线、不叠加遮罩); + * 再点同一项 → 确认(返回该项下标)。点卡片外=取消。 */ int idx = (y - g.row_y) / g.rowh; - if (idx >= 0 && idx < d->nitems) { d->cur = idx; d->result = idx; d->done = 1; } + if (idx >= 0 && idx < d->nitems) { + if (idx == d->cur) { d->result = idx; d->done = 1; } + else { d->cur = idx; dlg_repaint(d, th); } + } break; } /* 非选择:按钮命中 */ @@ -195,6 +250,7 @@ void meow_dialog_show(meow_dialog *d) if (d->done) break; usleep(16000); } + modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ } int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; } @@ -257,6 +313,7 @@ int meow_sheet_open(meow_sheet *s) int W = meow_ui_width(), H = meow_ui_height(); int ph = sheet_h(s, th); int py = H - ph, pad = 24; + modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原 */ /* 首次绘制:遮罩 + 底部面板(顶部圆角) */ meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest); @@ -273,6 +330,7 @@ int meow_sheet_open(meow_sheet *s) while (!s->done) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) { int y = e.b; if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外=取消 */ @@ -288,6 +346,7 @@ int meow_sheet_open(meow_sheet *s) if (s->done) break; usleep(16000); } + modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ return s->result; } void meow_sheet_free(meow_sheet *s) { if (s) free(s); } diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index b3b3ba5..fdcd3fc 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -179,6 +179,10 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } void meow_gfx_mark_full(void) { dirty_full(); } void meow_gfx_dirty_reset(void) { dirty_reset(); } +/* back_buf 每行字节数 / 位深(M2.71-bug3:模态/IME 整帧快照恢复用)。 */ +int meow_ui_back_buf_pitch(void) { return g_line; } +int meow_ui_back_buf_bpp(void) { return g_bpp; } + /* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它, * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ unsigned char *meow_ui_back_buf(void) { return g_back_buf; } @@ -393,6 +397,23 @@ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color) dirty_add(x1, y1, x2 - x1, y2 - y1); } +/* 圆角矩形半透明填充(M2.71-bug4):按圆角裁剪后逐像素混合 α。供「禁用变暗」等状态层用, + * 避免在圆角外的方形区域也叠色(旧 fill_rect_alpha 会在圆角外露出灰方块)。 */ +void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha) +{ + if (!g_init_ok) return; + if (alpha <= 0) return; + if (alpha >= 255) { meow_gfx_fill_round(x, y, w, h, r, color); return; } + if (w <= 0 || h <= 0) return; + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + for (int py = y1; py < y2; py++) + for (int px = x1; px < x2; px++) + if (rr_inside(px, py, x, y, w, h, r)) put_px_blend(px, py, color, alpha); + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + /* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) ================= * 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、 * 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */ @@ -462,7 +483,9 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d if (!g_init_ok || r <= 0) return; int sw = sweep_deg % 360; if (sw < 0) sw += 360; - if (sw >= 360) { /* 整环:bbox 距离带(无角度判定,最快) */ + /* 整环(|sweep|>=360):bbox 距离带(无角度判定,最快)。注意 sweep=360 → sw=0, + * 若靠 sw>=360 判整环会落下走弧分支只画一个点(顺带让 Radio 外圈/圆形进度轨道环渲染成点)。 */ + if (sweep_deg >= 360 || sweep_deg <= -360) { int r1 = r - th / 2, r2 = r + th / 2; if (r1 < 0) r1 = 0; int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1; @@ -478,6 +501,7 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d dirty_add(x1, y1, x2 - x1, y2 - y1); return; } + if (sw == 0) return; /* 零弧长:不画(sweep=0;整环已在上面处理) */ /* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */ int n = sw / 2 + 1; if (n < 2) n = 2; diff --git a/meow_ui_ime.c b/meow_ui_ime.c index ff60b95..87a5a8e 100644 --- a/meow_ui_ime.c +++ b/meow_ui_ime.c @@ -22,6 +22,36 @@ #define GAP 4 /* 键间距 */ #define GCOL 48 /* 列步进 = GW+GAP */ +/* M2.71-bug7:键盘区背景快照/恢复。旧实现关闭后键盘像素留在屏底(残影)。此处打开前拍下 + * 键盘区 [KB_TOP,H),关闭后回填 + 上屏 → 无残影。 */ +static unsigned char *g_ime_cap = NULL; +static int g_ime_cap_line = 0, g_ime_cap_h = 0; + +static void ime_capture(void) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(); + int h = meow_ui_height() - KB_TOP; + if (!bb || line <= 0 || h <= 0) { g_ime_cap = NULL; return; } + size_t n = (size_t)line * (size_t)h; + if (!g_ime_cap || g_ime_cap_line != line || g_ime_cap_h != h) { + free(g_ime_cap); + g_ime_cap = (unsigned char *)malloc(n); + g_ime_cap_line = line; g_ime_cap_h = h; + } + if (g_ime_cap) memcpy(g_ime_cap, bb + (size_t)line * KB_TOP, n); +} + +static void ime_restore(void) +{ + unsigned char *bb = meow_ui_back_buf(); + if (!g_ime_cap || !bb) return; + size_t n = (size_t)g_ime_cap_line * (size_t)g_ime_cap_h; + memcpy(bb + (size_t)g_ime_cap_line * KB_TOP, g_ime_cap, n); + meow_gfx_mark_dirty(0, KB_TOP, meow_ui_width(), g_ime_cap_h); + meow_ui_flush(); +} + typedef enum { K_CH, K_SHIFT, K_SPACE, K_BACK, K_ENTER, K_ESC } kkind; struct kbkey { int x, y, w, h; kkind kind; char ch; }; @@ -91,18 +121,18 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' '); add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>'); } - } else { /* MEOW_IME_NUM */ + } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=提交并关闭)。 + * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 */ add_ch_row(y, "1234567890"); add_ch_row(y + GCOL, "-/.:*#+="); { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; - add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); - add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); - add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_BACK, '<'); - add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_ENTER, '>'); /* 覆盖:回车 */ + add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); /* 取消/关闭(丢弃) */ + add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); /* 空格 */ + add_key(x + GCOL * 8, y + GCOL * 2, (GW + GAP) * 2 - GAP, GH, K_BACK, '<'); /* 退格 */ } - /* 单独一行回车 */ + /* 回车(OK=提交并关闭) */ { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -149,25 +179,27 @@ void meow_ime_draw(void) { if (!g_ime_active) return; const meow_theme *th = meow_theme_get(); - /* 面板 */ - meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, 0x1C1C22); - /* 预览行 */ - meow_ui_fill_rect(0, KB_TOP, 480, 42, 0x2A2A33); - char preview[256]; + /* M2.71-bug7:配色全走 meow_theme(深/浅主题自动切),不再硬编码黑底白键。 */ + meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, th->surfaceContainerHighest); /* 面板 */ + meow_ui_fill_rect(0, KB_TOP, 480, 42, th->surfaceContainerHigh); /* 回显条 */ + char preview[260]; /* g_ime_buf(≤255) + '_' + NUL → 需 ≥257,防截断告警 */ + /* 回显=用户已输入的字符(live echo,非候选/联想),随键入实时刷新。 */ snprintf(preview, sizeof preview, "%s_", g_ime_buf); - meow_ui_text(14, KB_TOP + 9, preview, th->font_body, 0xFFFFFF); + meow_ui_text(14, KB_TOP + 9, preview, th->font_body, th->onSurface); /* 键 */ for (int i = 0; i < g_nkeys; i++) { const struct kbkey *k = &g_keys[i]; - int is_fn = (k->kind != K_CH); - uint32_t bg = is_fn ? 0x33333C : 0xFFFFFF; + uint32_t bg, fg; + if (k->kind == K_ENTER) { bg = th->primary; fg = th->onPrimary; } /* OK=提交 */ + else if (k->kind == K_CH) { bg = th->surfaceContainerLow; fg = th->onSurface; } /* 字符键 */ + else { bg = th->primaryContainer; fg = th->onPrimaryContainer; } /* 功能键(ESC/SHIFT/空格/退格) */ meow_gfx_fill_round(k->x, k->y, k->w, k->h, 8, bg); char label[2] = { k->ch, 0 }; if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch) : (char)tolower((unsigned char)k->ch); - uint32_t fg = is_fn ? 0xFFFFFF : 0x1A1A1E; if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); } else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); } + else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "X", 20, fg); } else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); } } } @@ -199,6 +231,7 @@ void meow_ime_close(int result) g_ime_orig->buf[sizeof g_ime_orig->buf - 1] = 0; } g_ime_active = 0; g_ime_done = 0; + ime_restore(); /* M2.71-bug7:关闭还原键盘区背景,无残影 */ } void meow_ui_text_input_async(meow_ime_ctx *ctx) @@ -211,6 +244,7 @@ void meow_ui_text_input_async(meow_ime_ctx *ctx) g_maxlen = ctx->maxlen > 0 ? ctx->maxlen : 255; g_shift = 0; g_ime_result = 0; g_ime_done = 0; g_ime_active = 1; build_kb(ctx->mode); + ime_capture(); /* M2.71-bug7:打开前拍下键盘区背景(关闭即还原) */ meow_input_start(); meow_input_drain(); } @@ -223,6 +257,7 @@ int meow_ui_text_input(meow_ime_ctx *ctx) while (meow_ime_active()) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b); } meow_ime_draw(); diff --git a/meow_ui_input.c b/meow_ui_input.c index 80b32ae..a7bbcb2 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -37,6 +37,7 @@ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ #define QUEUE_N 128 #define MAX_DEVS 8 +#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */ /* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)── * 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理 @@ -126,6 +127,8 @@ static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 A static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ +/* M2.71-bug2/6:拖动过程事件上次上报坐标(坐标变化才入队 MEOW_EV_TOUCH_MOVE,防刷屏)。 */ +static int t_mv_x = -1, t_mv_y = -1; /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -138,22 +141,32 @@ static void touch_press(int x, int y) t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ + t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */ } -/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ +/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 + * M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE + * (坐标变了才报,省队列)。这两类事件供 slider 拖动 / 列表&页面滚动跟手。 */ static void touch_update(int x, int y) { if (t_down && !t_origin_valid && x >= 0 && y >= 0) { t_dx = x; t_dy = y; t_origin_valid = 1; + t_mv_x = x; t_mv_y = y; + push_ev(MEOW_EV_TOUCH_DOWN, x, y); } if (x >= 0) t_cx = x; if (y >= 0) t_cy = y; + if (t_down && t_origin_valid && t_cx >= 0 && t_cy >= 0 && + (t_cx != t_mv_x || t_cy != t_mv_y)) { + t_mv_x = t_cx; t_mv_y = t_cy; + push_ev(MEOW_EV_TOUCH_MOVE, t_cx, t_cy); + } } static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; @@ -194,7 +207,7 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; } /* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)---- @@ -221,6 +234,7 @@ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) } else { t_down = 0; t_press_valid = 0; t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; + t_mv_x = -1; t_mv_y = -1; } } @@ -301,6 +315,19 @@ void meow_input_start(void) /* ---- 事件循环 ---- */ void meow_ui_request_exit(void) { g_exit = 1; } +/* M2.71-bug5:App 活动信号(节流 ≤2/s 写标记文件,限 I/O)。 + * 仅真机写(宿主/qemu 无 launcher 消费,避免噪音);launcher 前台 app 循环消费它重置息屏计时。 */ +static uint64_t g_activity_last_ms = 0; +void meow_ui_activity_ping(void) +{ + uint64_t now = now_ms(); + if (now - g_activity_last_ms < 500) return; /* 节流:任意交互连续触摸也只 ~2 次/s */ + g_activity_last_ms = now; + if (!meow_gfx_is_device()) return; /* 离屏/宿主:无 launcher 消费 */ + int fd = open(MEOW_APP_ACTIVITY_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { if (write(fd, "1", 1) < 0) { } close(fd); } +} + void meow_ui_run(const meow_ui_events *ev) { if (ev) { g_events = *ev; g_events_set = 1; } @@ -319,6 +346,7 @@ void meow_ui_run(const meow_ui_events *ev) while (meow_input_pop(&e)) { if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */ got = 1; + meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */ int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */ if (e.type == MEOW_EV_TAP) { if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起) @@ -354,6 +382,17 @@ void meow_ui_run(const meow_ui_events *ev) /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 * APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */ meow_ui_sys_notif_pull(); + } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { + /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, + * on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按 + * 误锁拖拽)。模态(dialog/sheet)期间本主循环被阻塞,天然不在此分发。 */ + if (in_startup) continue; + if (meow_ime_active()) continue; + if (g_events_set && g_events.on_touch_down && e.type == MEOW_EV_TOUCH_DOWN) + g_events.on_touch_down(e.a, e.b); + if (g_events_set && g_events.on_touch_move && e.type == MEOW_EV_TOUCH_MOVE) + g_events.on_touch_move(e.a, e.b); + got = 1; /* 交互事件:不睡眠,保持跟手帧同步 */ } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { if (in_startup) continue; /* 启动窗口进度条静音 */ int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 9a4a610..62dbf3c 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -108,9 +108,14 @@ void meow_gfx_dirty_reset(void); /* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */ unsigned char *meow_ui_back_buf(void); +/* back_buf 的每行字节数(pitch)与位深(16/32)。M2.71-bug3:模态/IME 快照恢复用。 */ +int meow_ui_back_buf_pitch(void); +int meow_ui_back_buf_bpp(void); /* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */ void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color); +/* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */ +void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha); /* §5.8.6 batch2:软渲圆形/线段/圆弧环原语(无 GPU、无 -lm,内部整数/查表实现)。 * 供 Radio(外圈/内点)、Badge(红点)、ProgressBar(环形轨道/进度弧)、Checkbox(勾线段)使用。 */ @@ -131,6 +136,8 @@ int meow_gfx_mfont_ready(void); #define MEOW_EV_TAP 2 /* a=x, b=y(触摸抬起落点) */ #define MEOW_EV_SWIPE 3 /* a=MEOW_SWIPE_*, b=距离(px) */ #define MEOW_EV_SWIPE_PROGRESS 4 /* a=按下X(t_dx), b=当前X(t_cx);dx=b-a(>0=左缘向内划=返回手势) */ +#define MEOW_EV_TOUCH_DOWN 6 /* a=x, b=y:按下后首个有效坐标(拖动起点,M2.71-bug2/6) */ +#define MEOW_EV_TOUCH_MOVE 7 /* a=x, b=y:拖动过程坐标变化(坐标变才入队;跟手用) */ typedef struct meow_input_ev { uint8_t type; @@ -146,6 +153,12 @@ void meow_input_inject(int type, int a, int b); /* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ void meow_input_drain(void); +/* ---- M2.71-bug5:App 活动 → 通知 launcher 重置息屏计时 ---- + * launcher 前台 app 会话的 app_last_input 只在物理键边沿重置(它看不到应用内触摸)。SDK 在 + * 收到任意交互事件(tap/swipe/拖动/IME/模态)时节流 touch 出 /tmp/meow_app_activity 标记; + * launcher app 循环探测到即 unlink + 重置计时(若已自动息屏则照 key-wake 路径唤醒)。 */ +void meow_ui_activity_ping(void); + /* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)---- * 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw * 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */ diff --git a/meow_ui_widget.c b/meow_ui_widget.c index f4f5bb3..ef9c5d7 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -26,6 +26,11 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u +/* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。 + * 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */ +#define MEOW_PROGRESS_ARC_THK 4 +#define MEOW_PROGRESS_ARC_MARGIN (MEOW_PROGRESS_ARC_THK / 2 + 1) + /* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ static uint32_t blend_color(uint32_t c, uint32_t o, int t) @@ -73,6 +78,9 @@ struct meow_widget { char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */ int nitems; /* 上述项数 */ int selected; /* W_NAVBAR / W_TABBAR 选中下标 */ + /* M2.71-bug8:W_LIST 行点击回调(可选)。 */ + meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */ + void *tap_ctx; /* 回调 ctx */ }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- @@ -584,7 +592,13 @@ meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; - t->type = W_PROGRESS; t->x = cx; t->y = cy; t->w = r; t->h = r; + /* bbox = 整圆像素外接框(中心 ± (r + margin)):使 w->x/y/w/h 即真实绘制范围, + * 于是 meow_progress_set 的 mark_dirty 与 App 的 flush_region(w->x,w->y,w->w,w->h) + * 都天然对齐,不漏刷/不多刷(修复 bgg M2.71 圆形进度脏区)。 */ + int m = MEOW_PROGRESS_ARC_MARGIN; + t->type = W_PROGRESS; + t->x = cx - r - m; t->y = cy - r - m; + t->w = 2 * (r + m); t->h = 2 * (r + m); t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->variant = 1; /* 圆形 */ @@ -791,6 +805,34 @@ int meow_list_hit_index(meow_widget *w, int x, int y) return (idx >= 0 && idx < w->count) ? idx : -1; } +/* M2.71-bug8:选中行(越界 clamp)+ 标脏(高亮刷新)。 */ +void meow_list_set_selected(meow_widget *w, int idx) +{ + if (!w || w->type != W_LIST) return; + if (idx < 0) idx = 0; + if (w->count > 0 && idx >= w->count) idx = w->count - 1; + if (idx == w->sel) return; + w->sel = idx; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_list_selected(meow_widget *w) { return (w && w->type == W_LIST) ? w->sel : -1; } + +void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx) +{ + if (!w || w->type != W_LIST) return; + w->tap_cb = cb; w->tap_ctx = ctx; +} + +/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1(不置选中)。 */ +int meow_list_tap_at(meow_widget *w, int x, int y) +{ + int idx = meow_list_hit_index(w, x, y); + if (idx < 0) return -1; + meow_list_set_selected(w, idx); + if (w->tap_cb) w->tap_cb(idx, w->tap_ctx); + return idx; +} + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -816,7 +858,9 @@ void meow_slider_set(meow_widget *w, int val) { if (!w || w->type != W_SLIDER) return; if (val < w->min) val = w->min; if (val > w->max) val = w->max; + if (val == w->val) return; w->val = val; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 滑块/轨道随值变化:标脏 */ } int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; } void meow_switch_set(meow_widget *w, int on) @@ -899,8 +943,8 @@ void meow_widget_draw(meow_widget *w) if (sw > 0 && sc) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, sc); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - /* 禁用:整体变暗(状态层向 onSurface 叠 α) */ - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + /* 禁用:整体变暗(圆角 α,防在圆角外叠出灰方块——M2.71-bug4) */ + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_SLIDER: { @@ -922,7 +966,7 @@ void meow_widget_draw(meow_widget *w) int knob_r = w->h / 2 - 3; int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3; meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->h / 2, th->onSurface, 90); break; } case W_LIST: { @@ -995,7 +1039,7 @@ void meow_widget_draw(meow_widget *w) int tw = meow_ui_text_width(w->buf, th->font_body); meow_ui_fill_rect(tx + tw, ty, 2, meow_ui_text_height(th->font_body), th->primary); } - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_CARD: { @@ -1008,7 +1052,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 24); if (w->nchildren > 0 && w->children[0]) meow_widget_draw(w->children[0]); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_DIVIDER: { @@ -1046,7 +1090,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline); } if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, sz, sz, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, sz, sz, r, th->onSurface, 90); break; } case W_RADIO: { @@ -1054,13 +1098,16 @@ void meow_widget_draw(meow_widget *w) int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2; meow_gfx_ring_arc(ccx, ccy, 12, 2, 0, 360, sel ? th->primary : th->outline); if (sel) meow_gfx_fill_circle(ccx, ccy, 6, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->w / 2, th->onSurface, 90); break; } case W_PROGRESS: { - if (w->variant == 1) { /* 圆形 */ - int ccx = w->x, ccy = w->y, rr = w->w; - int thk = 4; + if (w->variant == 1) { /* 圆形(几何 = 整圆像素外接框;由此解回圆心/半径) */ + int thk = MEOW_PROGRESS_ARC_THK; + int m = MEOW_PROGRESS_ARC_MARGIN; + int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2; + int rr = w->w / 2 - m; /* 与 create 一致:回到调用方传入的半径 r */ + if (rr <= 0) break; meow_gfx_ring_arc(ccx, ccy, rr, thk, 0, 360, th->surfaceVariant); if (w->value < 0) { /* 不定:按时间推进一段弧(App 在 on_tick 里重绘驱动) */ int start = (int)((meow_ui_time_ms() / 30) % 360); @@ -1094,7 +1141,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, tc); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_TOPAPPBAR: { diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c index 168ef5b..4b1b447 100644 --- a/test_meow_ui_widgets.c +++ b/test_meow_ui_widgets.c @@ -144,11 +144,18 @@ int main(void) assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */ meow_list_set_item_h(lc, 50); assert(meow_widget_hit(lc, 30, 470) == 1); + /* M2.71-bug8:行选中 + 命中点击回调 */ + meow_list_set_selected(lc, 5); + assert(meow_list_selected(lc) == 5); + /* 验证 tap_at 返回命中下标并置选中 */ + int hidx = meow_list_tap_at(lc, 30, 480); + assert(hidx == ((480 - 460 + meow_list_scroll(lc)) / 50)); + assert(meow_list_selected(lc) == hidx); /* LazyRow 横向 */ meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL)); meow_list_scroll_by(lr, 200); assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60)); - printf("PASS lazy list: scroll clamp/virtualize/hit_index (col+row)\n"); + printf("PASS lazy list: scroll clamp/virtualize/hit_index/tap_at/select (col+row)\n"); /* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */ meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one"); diff --git a/test_sdk_fixes.c b/test_sdk_fixes.c new file mode 100644 index 0000000..bd7c42b --- /dev/null +++ b/test_sdk_fixes.c @@ -0,0 +1,158 @@ +/* test_sdk_fixes.c — M2.71 修复项宿主验证(离屏 480x720,16bpp)。 + * 覆盖: + * bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗); + * bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提); + * bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影); + * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include +#include + +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + assert(bb && line > 0); + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12; } + +/* ---- 为阻塞模态/IME 在后台注入事件 ---- */ +static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0; +static void *inject_thread(void *arg) +{ + (void)arg; + usleep((useconds_t)g_inj_delay); + meow_input_inject(MEOW_EV_TAP, g_inj_tap_x, g_inj_tap_y); + return NULL; +} +static void inject_after(int ms, int x, int y) +{ + pthread_t th; g_inj_tap_x = x; g_inj_tap_y = y; g_inj_delay = ms; + pthread_create(&th, NULL, inject_thread, NULL); + pthread_detach(th); +} + +static void test_disabled_corner(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_widget *b = meow_btn_create(100, 100, 200, 60, "DISABLED"); + meow_widget_set_enabled(b, 0); /* 禁用:old 用方形 α 盖整 bbox → 圆角外露灰 */ + meow_widget_draw(b); + meow_ui_flush(); + int r, g, bb; + rd_px(100, 100, &r, &g, &bb); /* bbox 左上角(圆角外):应为页面背景 */ + printf("[bug4] disabled corner rgb=%02X%02X%02X (bg=%02X%02X%02X) ", + r, g, bb, (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(bb, th->bg & 0xFF)); + printf("PASS\n"); + meow_widget_free(b); +} + +static void test_circular_extent(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + /* r=30, thk=4 → 环外缘 = r+thk/2 = 32;create 几何外接框 = 中心±(r+margin)=±33 */ + int cx = 240, cy = 200, r = 30; + meow_widget *pg = meow_progress_circular_create(cx, cy, r, 100); + meow_widget_draw(pg); + meow_ui_flush(); + int rd, g, b; + rd_px(cx - 32, cy, &rd, &g, &b); /* 最左环外缘:应被画(非背景) */ + int bg_r = (th->bg >> 16) & 0xFF, bg_g = (th->bg >> 8) & 0xFF, bg_b = th->bg & 0xFF; + printf("[bug1] ring-left-extent rgb=%02X%02X%02X bg=%02X%02X%02X ", rd, g, b, bg_r, bg_g, bg_b); + assert(!(near(rd, bg_r) && near(g, bg_g) && near(b, bg_b))); /* 环带已画到外缘 */ + printf("PASS\n"); fflush(stdout); + meow_widget_free(pg); +} + +static void test_modal_restore(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?"); + /* 两次 open→cancel:背景应两次均还原(scrim 不累积、无残影) */ + for (int i = 0; i < 2; i++) { + inject_after(30, 1, 1); /* (1,1) 在居中卡片外 → 取消 */ + meow_dialog_show(d); + int r, g, b; + rd_px(400, 100, &r, &g, &b); /* 卡片外右上:应 == 背景 */ + printf("[bug3] open%d cancel bg rgb=%02X%02X%02X bg=%02X%02X%02X ", i, r, g, b, + (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF)); + } + printf("PASS\n"); + meow_dialog_free(d); +} + +/* IME 回显同步回调 */ +static char g_echo[64] = ""; +static void ime_cb(const char *t, void *ctx) { (void)ctx; snprintf(g_echo, sizeof g_echo, "%s", t); } + +static void test_ime_echo_and_restore(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_ime_ctx c; memset(&c, 0, sizeof c); + c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb; + /* --- 异步:验『回显/输入框实时同步(on_change 随键入触发)』--- */ + meow_ui_text_input_async(&c); + assert(meow_ime_active() == 1); + meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 第3行第1键 = 'A'(shift 关 → 缓冲 'A') */ + meow_ime_handle_tap(20, 580); + assert(strcmp(g_echo, "A") == 0 && strcmp(meow_ime_text(), "A") == 0); + printf("[bug7] ime echo live-sync after 'A' -> \"%s\" (on_change fired) PASS\n", g_echo); fflush(stdout); + meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */ + meow_ime_handle_tap(20, 680); + assert(meow_ime_active() == 0); + /* --- 阻塞:验『关闭后键盘区背景还原』--- */ + meow_ui_clear(th->bg); + meow_ui_flush(); + inject_after(30, 20, 680); /* ESC 关闭 */ + int rc = meow_ui_text_input(&c); + assert(rc == 0); /* 取消 */ + int r, g, b; + rd_px(240, 600, &r, &g, &b); /* 键盘区内一点:应 == 背景 */ + printf("[bug7] ime close restore px rgb=%02X%02X%02X bg=%02X%02X%02X ", r, g, b, + (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF)); + printf("PASS\n"); +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + test_disabled_corner(); + test_circular_extent(); + test_modal_restore(); + test_ime_echo_and_restore(); + meow_ui_shutdown(); + printf("ALL PASS\n"); + return 0; +} From 6b55cd012ba46b0ecbb3269525e53337cf42ad79 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 00:19:49 +0800 Subject: [PATCH 19/49] =?UTF-8?q?chore:=20=E8=A1=A5=E5=85=A5=20test=5Fsb?= =?UTF-8?q?=5Fcollapse.c(agent-146=20=E5=85=A8=E5=B1=8F=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E6=94=B6=E5=90=88=E5=AE=BF=E4=B8=BB=E6=B5=8B,=20?= =?UTF-8?q?=E6=AD=A4=E5=89=8D=E6=BC=8F=E5=85=A5=E5=BA=93)=20+=20docs=20?= =?UTF-8?q?=E5=AD=97=E4=BD=93=E6=A1=A3=E4=BD=8D=E5=AE=9A=E6=A1=88=E9=94=9A?= =?UTF-8?q?=E7=82=B9(=E2=86=92FONT=5FSYSTEM.md)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_sb_collapse.c | 124 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 test_sb_collapse.c diff --git a/test_sb_collapse.c b/test_sb_collapse.c new file mode 100644 index 0000000..ffbc7cb --- /dev/null +++ b/test_sb_collapse.c @@ -0,0 +1,124 @@ +/* test_sb_collapse.c — 宿主验证「全屏状态栏收合」纯逻辑(agent-146,不依赖 /dev/fb0/字库): + * 1. 顶条模式上报:App 进程(system_mode=0)set_collapsed/set_fullscreen → 写 /tmp/meowapp_sbar + * ('0'展开/'1'收合/'2'隐藏),供 launcher 感知 App 运行时全屏/收合态。 + * 2. 热区保留判定:状态栏激活(含收合)时 meow_ui_statusbar_hotzone()==MUI_SBAR_H(28)(收合≠禁热区, + * 渲染与热区事件分离);仅全屏隐藏(inset0)时缩到 MEOW_SBAR_HOTZONE_FS(4)。 + * 3. 收合状态机:system_mode=1 + animate(200) + set_collapsed(1) → animating()==1, + * collapse_offset() 随时间推进 → 28(收合出屏);set_collapsed(0) → offset 回 0(恢复)。 + * 4. 收合 inset:collapsed → meow_ui_statusbar_height()==MUI_SBAR_COLLAPSE_H(4)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log + 打桩 font/global): + * gcc -o test_sb_collapse test_sb_collapse.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_sys.h" + +#include +#include +#include +#include +#include +#include + +#define SBAR_MODE_FILE "/tmp/meowapp_sbar" + +static int read_mode(void) +{ + int fd = open(SBAR_MODE_FILE, O_RDONLY); + if (fd < 0) return -1; + char ch[8]; int n = read(fd, ch, sizeof ch - 1); + close(fd); + if (n <= 0) return -1; + return ch[0] == '1' ? 1 : (ch[0] == '2' ? 2 : 0); +} + +int main(void) +{ + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + /* 初始 App 模式(system_mode 默认 0) */ + assert(meow_ui_sys_render_enabled() == 0); + + /* ---- 1) 顶条模式上报 ---- */ + meow_ui_set_fullscreen(0); meow_ui_statusbar_set_collapsed(0); + assert(read_mode() == 0); /* 展开 */ + meow_ui_statusbar_set_collapsed(1); + assert(read_mode() == 1); /* 收合 */ + meow_ui_statusbar_set_collapsed(0); + meow_ui_set_fullscreen(1); + assert(read_mode() == 2); /* 隐藏 */ + meow_ui_set_fullscreen(0); + printf("PASS mode-file: app writes /tmp/meowapp_sbar (0=expanded 1=collapsed 2=hidden)\n"); + + /* ---- 2a) 激活(含收合)时热区保留 28 ---- */ + meow_ui_statusbar_set_collapsed(1); + assert(meow_ui_statusbar_collapsed() == 1); + assert(meow_ui_statusbar_hotzone() == MUI_SBAR_H); /* 收合仍监听顶部 28px(渲染与热区分离) */ + assert(meow_ui_statusbar_height() == MUI_SBAR_COLLAPSE_H); /* 收合 inset=4(提示线) */ + assert(meow_ui_sys_statusbar_active() == 1); /* 收合仍视为状态栏激活 */ + printf("PASS hotzone-retain: collapsed -> hotzone=%d(28) inset=%d(4) active\n", + meow_ui_statusbar_hotzone(), meow_ui_statusbar_height()); + + /* ---- 2b) 全屏隐藏(immersive)时热区缩小 ---- */ + meow_ui_statusbar_set_collapsed(0); + meow_ui_set_fullscreen(1); + int fs_hot = meow_ui_statusbar_hotzone(); + assert(fs_hot == MEOW_SBAR_HOTZONE_FS); /* inset0 → 4 */ + printf("PASS hotzone-shrink: fullscreen-hidden -> hotzone=%d(fs=4)\n", fs_hot); + meow_ui_set_fullscreen(0); + meow_ui_statusbar_set_collapsed(0); + + /* ---- 3) 收合状态机(launcher / system_mode=1)---- */ + meow_ui_sys_set_system_mode(1); + assert(meow_ui_sys_render_enabled() == 1); + meow_ui_set_refresh_always_max(1); /* 宿主:绕过帧率门控,逐次 flush 可推进 */ + + meow_ui_statusbar_set_collapsed(0); /* 复位 */ + meow_ui_set_fullscreen(0); + meow_ui_statusbar_animate(200); /* 上移过渡 200ms */ + assert(meow_ui_statusbar_animating() == 0); + meow_ui_statusbar_set_collapsed(1); /* 目标收合 */ + assert(meow_ui_statusbar_animating() == 1); /* 未到位 → 过渡中 */ + assert(meow_ui_statusbar_collapse_offset() == 0); + + /* 驱动 present(标顶条左上 1px 唤醒 + 上屏)并让时间流逝,验证 offset 推进 */ + int off_prev = 0, progressed = 0; + for (int i = 0; i < 60; i++) { + meow_ui_flush_region(0, 0, 1, 1); + usleep(15000); /* ~15ms/帧,模拟过渡期高频刷 */ + int off = meow_ui_statusbar_collapse_offset(); + if (off > off_prev) progressed = 1; + off_prev = off; + if (!meow_ui_statusbar_animating()) break; /* 到 28 稳定 */ + } + assert(progressed == 1); + assert(meow_ui_statusbar_collapse_offset() >= MUI_SBAR_H); + assert(meow_ui_statusbar_animating() == 0); + assert(meow_ui_statusbar_height() == MUI_SBAR_COLLAPSE_H); /* 收合稳定:inset=4 */ + printf("PASS collapse-enter: offset 0->%d (animating 1->0)\n", + meow_ui_statusbar_collapse_offset()); + + /* 退出全屏(恢复):offset 回 0 */ + meow_ui_statusbar_set_collapsed(0); + assert(meow_ui_statusbar_animating() == 1); + int back = 0; + for (int i = 0; i < 60; i++) { + meow_ui_flush_region(0, 0, 1, 1); + usleep(15000); + back = meow_ui_statusbar_collapse_offset(); + if (!meow_ui_statusbar_animating()) break; + } + assert(back == 0 && meow_ui_statusbar_animating() == 0); + assert(meow_ui_statusbar_height() == MUI_SBAR_H); /* 恢复:inset=28 */ + printf("PASS collapse-exit: offset ->0 (inset back to 28)\n"); + + /* 清理:宿主测试产物 */ + unlink(SBAR_MODE_FILE); + + printf("ALL PASS\n"); + meow_ui_shutdown(); + return 0; +} From 4afd1abb4a67c6be2b62b9893299c7886fd0d614 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 01:27:01 +0800 Subject: [PATCH 20/49] =?UTF-8?q?fix(sdk/hello):=20P1=20z-order=E9=87=8D?= =?UTF-8?q?=E5=8F=A0(FAB=E8=A2=AB=E4=B8=8B=E5=B1=82=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E6=8A=B9=E6=8E=89,d=5Fredraw=E2=86=92d=5Fpai?= =?UTF-8?q?nt=5Fregion=E6=8C=89z=E6=A0=88=E9=87=8D=E7=94=BB=E9=87=8D?= =?UTF-8?q?=E5=8F=A0=E5=8C=BA)=20+=20P2=20list=E7=84=A6=E7=82=B9(=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E9=9D=9Elist=E5=8C=BA=E5=9F=9F=E9=87=8A=E6=94=BE?= =?UTF-8?q?=E7=84=A6=E7=82=B9=E6=BB=9A=E9=A1=B5=E9=9D=A2)=20+=20P3=20list?= =?UTF-8?q?=E8=84=8F=E5=8C=BA(W=5FLIST=E8=A3=81=E5=89=AA=E8=A7=86=E5=8F=A3?= =?UTF-8?q?,=E6=A0=87=E9=A2=98=E6=A0=8F/=E5=BA=95=E9=83=A8=E6=97=A0?= =?UTF-8?q?=E6=AE=8B=E7=95=99)=20(agent-166)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui_widget.c | 34 +++++++--- test_p1p2p3_repro.c | 148 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 test_p1p2p3_repro.c diff --git a/meow_ui_widget.c b/meow_ui_widget.c index ef9c5d7..c6a473f 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -984,26 +984,40 @@ void meow_widget_draw(meow_widget *w) int last = (item > 0) ? (off + vp) / item + 1 : 0; if (first < 0) first = 0; if (last > w->count) last = w->count; + /* 视口绝对边界:列表只在其内绘制,杜绝越界泄漏(部分行滚出视口时把填充/分隔线/文本 + * 画到视口外 → 标脏并上屏 → 标题栏/底部残留脏区,P3)。 */ + int vx0 = w->x, vy0 = w->y, vx1 = w->x + w->w, vy1 = w->y + w->h; + int thh = meow_ui_text_height(th->font_body); for (int i = first; i < last; i++) { char label[64] = ""; if (!w->src || !w->src(i, label, (int)sizeof label, w->ctx)) continue; int pos = i * item - off; /* 相对容器起点的可视位置 */ if (w->horizontal) { int rx = w->x + pos; - if (rx >= w->x + w->w) break; - if (i == w->sel) meow_ui_fill_rect(rx, w->y, item, w->h, th->primary); + if (rx >= vx1) break; /* 完全在右缘外 */ + if (rx + item <= vx0) continue; /* 完全在左缘外(滚出残余) */ + int cl = rx > vx0 ? rx : vx0; /* 可见左界 */ + int cr = (rx + item) < vx1 ? (rx + item) : vx1; /* 可见右界 */ + if (i == w->sel) meow_ui_fill_rect(cl, w->y, cr - cl, w->h, th->primary); uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; - meow_ui_text_center(rx + item / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, - label, th->font_body, col); - meow_ui_fill_rect(rx + item - 1, w->y, 1, w->h, th->onSurfaceVariant); + int ty = w->y + (w->h - thh) / 2; + if (rx >= vx0 && rx + item <= vx1) /* 仅整项可见才画文本(防泄漏) */ + meow_ui_text_center(rx + item / 2, ty, label, th->font_body, col); + int sxp = rx + item - 1; /* 分隔线:仅当落在可见区 */ + if (sxp >= cl && sxp < cr) meow_ui_fill_rect(sxp, w->y, 1, w->h, th->onSurfaceVariant); } else { int ry = w->y + pos; - if (ry >= w->y + w->h) break; - if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, item, th->primary); + if (ry >= vy1) break; /* 完全在底缘外 */ + if (ry + item <= vy0) continue; /* 完全在顶缘外(滚出残余) */ + int cl = ry > vy0 ? ry : vy0; /* 可见顶界 */ + int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */ + if (i == w->sel) meow_ui_fill_rect(w->x, cl, w->w, cr - cl, th->primary); uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; - meow_ui_text(w->x + pad, ry + (item - meow_ui_text_height(th->font_body)) / 2, - label, th->font_body, col); - meow_ui_fill_rect(w->x, ry + item - 1, w->w, 1, th->onSurfaceVariant); + int ty = ry + (item - thh) / 2; + if (ty >= vy0 && ty + thh <= vy1) /* 文本不越界才画(防标题/底部泄漏) */ + meow_ui_text(w->x + pad, ty, label, th->font_body, col); + int sy = ry + item - 1; /* 分隔线:仅当落在可见区 */ + if (sy >= cl && sy < cr) meow_ui_fill_rect(w->x, sy, w->w, 1, th->onSurfaceVariant); } } if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); diff --git a/test_p1p2p3_repro.c b/test_p1p2p3_repro.c new file mode 100644 index 0000000..eaefad4 --- /dev/null +++ b/test_p1p2p3_repro.c @@ -0,0 +1,148 @@ +/* test_p1p2p3_repro.c — 宿主复现并校验 P1/P2/P3 三个真机问题的修复(离屏 480x720,16bpp)。 + * + * 方法:#include 真实 `hello.c`(改 hello 的 main 名),直接驱动 demo 的真实函数 + * (demo_paint / d_redraw / demo_touch_down / demo_rebuild_to),对 back_buf 做像素断言。 + * 这样测的是「真实 demo 代码 + 真实 SDK」,不是模拟桩。不依赖 /dev/fb0 / 字库 / launcher。 + * + * 覆盖: + * P1 z-order 叠层:圆形进度条(实时重绘)与 FAB 浮层重叠时,局部重绘不再清掉/盖住 FAB。 + * 旧 d_redraw=清底+只画单控件 → FAB 重叠区被抹成背景;新 d_redraw=d_paint_region + * 重画重叠区完整 z 栈(内容→标题→slider值→chrome)→ FAB 保持。 + * P2 list 焦点:命中非 list 区域时释放列表焦点、切页滚动(不再沿用旧焦点继续滚列表)。 + * P3 list 脏区:W_LIST 绘制已裁剪到视口;滚动(非整行偏移)+选中行不再向标题间隙/底缘下泄漏 + * 填充(此前泄漏会标脏并上屏 → 标题栏/底部残留)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime/img/config/ + * notify/ipc + 打桩 font/global(test_host_stub) + stb_image): + * gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -Dmain=hello_app_main \ + * -o test_p1p2p3_repro test_p1p2p3_repro.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c stb_image.c \ + * -I. -I../hello_app -I../meow_launcherd -pthread -lm + * 运行:MEOW_UI_DUMP=/tmp/repro.ppm ./test_p1p2p3_repro (optional,dump 最终屏幕) + * + * 修复前(无 P1/P2/P3 修法)预期输出:RESULT: 4 FAIL(S); + * 修复后:RESULT: ALL PASS。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include + +#define main hello_app_main /* 让 hello.c 的 main 改名,由本文件提供 main */ +#include "hello.c" +#undef main + +/* ---- 像素读取 ---- */ +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb) { *r=*g=*b=0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12; } +static int is_bg(int r, int g, int b) +{ + const meow_theme *th = meow_theme_get(); + int br = (th->bg >> 16) & 0xFF, bg = (th->bg >> 8) & 0xFF, bb = th->bg & 0xFF; + return near(r, br) && near(g, bg) && near(b, bb); +} +static void pr_px(const char *tag, int x, int y) +{ + int r, g, b; rd_px(x, y, &r, &g, &b); + printf(" %-26s px(%d,%d)=%02X%02X%02X %s\n", tag, x, y, r, g, b, is_bg(r, g, b) ? "[bg]" : "[ink]"); +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/repro.ppm", 1); + meow_ui_opts o = { 480, 720 }; + if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } + meow_ui_activity_push(ACT_DEMO); /* demo 控件 owner=ACT_DEMO,命中/绘制对齐 */ + demo_init(); /* 建造 chrome + 内容(scroll=0),owner=ACT_DEMO */ + + int fails = 0; + + /* ================= P1:局部重绘不盖浮层(FAB) ================= + * 场景:scroll=0 时圆形进度(413..453,605..645) 与 FAB(406..462,616..672) 重叠。 + * demo_paint 先画内容后画 chrome → 场景正确 FAB 在上。随后「局部重绘进度条」: + * 旧(清底+只画进度条+flush 其矩形)→ FAB 重叠区被清掉; + * 新(清底+重画重叠区内所有控件=hello d_redraw/d_paint_region)→ FAB 保持。 */ + demo_paint(); + int fx = r_fab.x + 28, fy = r_fab.y + 6; /* FAB 内、远离 "+" 文本处 */ + int s0r, s0g, s0b; rd_px(fx, fy, &s0r, &s0g, &s0b); + printf("P1 scene (demo_paint) FAB-overlap "); pr_px("scene", fx, fy); + if (is_bg(s0r, s0g, s0b)) + printf(" [warn] expected FAB ink at scene, got bg — geometry assumptions off\n"); + /* 旧式局部重绘:清底+只画进度条(复现根因) */ + { + const meow_theme *th = meow_theme_get(); + meow_progress_set(g_dprc, 30); + meow_ui_fill_rect(r_prc.x, r_prc.y, r_prc.w, r_prc.h, th->bg); + meow_widget_draw(g_dprc); + meow_ui_flush_region(r_prc.x, r_prc.y, r_prc.w, r_prc.h); + } + int obr, obg, obb; rd_px(fx, fy, &obr, &obg, &obb); + printf("P1 old-partial (clear+single draw) "); pr_px("old", fx, fy); + if (is_bg(obr, obg, obb)) printf(" [BUG-CONFIRMED] old path erased FAB overlap (bg)\n"); + else printf(" [note] old path did NOT erase FAB here (color %02X%02X%02X)\n", obr, obg, obb); + + /* 新式局部重绘(真实 hello d_redraw → d_paint_region,重画重叠栈)*/ + demo_paint(); /* 还原场景 */ + meow_progress_set(g_dprc, 30); + d_redraw(g_dprc, r_prc); /* hello 的局部重绘入口 */ + int nbr, nbg, nbb; rd_px(fx, fy, &nbr, &nbg, &nbb); + printf("P1 new-partial (d_redraw stack) "); pr_px("new", fx, fy); + if (near(s0r, nbr) && near(s0g, nbg) && near(s0b, nbb)) + printf(" P1-PASS FAB preserved over live progress redraw\n"); + else { printf(" P1-FAIL FAB not preserved (scene %02X%02X%02X vs new %02X%02X%02X)\n", + s0r, s0g, s0b, nbr, nbg, nbb); fails++; } + + /* ================= P2:列表焦点按本次触摸绑定/释放 ================= + * demo_touch_down 命中非 list 区域时应释放列表焦点 → 页滚动,而不是继续滚列表。 */ + g_demo_focus = 1; /* 模拟此前点过列表(焦点留存) */ + g_demo = 1; /* demo 页已进入(demo_touch_down 前置闸门) */ + demo_touch_down(240, 300); /* 非 list 非 slider 的内容区 */ + printf("P2 touch_down(240,300) with focus=1 -> drag_list=%d drag_page=%d focus=%d\n", + g_drag_list, g_drag_page, g_demo_focus); + if (g_drag_list == 0 && g_drag_page == 1 && g_demo_focus == 0) + printf(" P2-PASS non-list touch releases focus, drags page\n"); + else { printf(" P2-FAIL (expected drag_page=1 focus=0; got drag_list=%d drag_page=%d focus=%d)\n", + g_drag_list, g_drag_page, g_demo_focus); fails++; } + + /* ================= P3:列表滚动 W_LIST 视口外泄漏 ================= + * 把页面 scroll 拉到底,列表上屏;再把列表 scroll 设非整行倍数 + 选中一个跨底/跨顶行, + * 观察视口外(标题间隙/底缘下)是否漏出选中行填充(标脏泄漏 = 标题栏/底部卡脏区)。 */ + demo_rebuild_to(100000); /* clamp 到 max,列表上屏 */ + printf("P3 list rect = (%d,%d,%d,%d)\n", r_list.x, r_list.y, r_list.w, r_list.h); + /* 顶部泄漏:选中行0 + scroll=20 → 行0 ry=top-20,填充越顶泄漏 */ + meow_list_set_selected(g_dlc, 0); meow_list_set_scroll(g_dlc, 20); + d_redraw(g_dlc, r_list); + int ux = r_list.x + 10, uy = r_list.y - 5; /* 列表顶上方 5px(标题间隙) */ + int ur, ug, ub; rd_px(ux, uy, &ur, &ug, &ub); + printf("P3 top-leak "); pr_px("above-top", ux, uy); + if (is_bg(ur, ug, ub)) printf(" P3-top-PASS no leak into title gap\n"); + else { printf(" P3-top-FAIL leaked into title gap\n"); fails++; } + /* 底部泄漏:选中行4 + scroll=20 → 行4 ry=590..630 越底泄漏 */ + meow_list_set_selected(g_dlc, 4); meow_list_set_scroll(g_dlc, 20); + d_redraw(g_dlc, r_list); + int dx = r_list.x + 10, dy_ = r_list.y + r_list.h + 5; /* 列表底下方 5px */ + int dr, dg, db; rd_px(dx, dy_, &dr, &dg, &db); + printf("P3 bot-leak "); pr_px("below-bottom", dx, dy_); + if (is_bg(dr, dg, db)) printf(" P3-bottom-PASS no leak below list\n"); + else { printf(" P3-bottom-FAIL leaked below list\n"); fails++; } + + printf(fails ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", fails); + return fails; +} From 06a69ef7d785b6ecd1aa9a8b787bb0ada1202269 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 02:01:56 +0800 Subject: [PATCH 21/49] =?UTF-8?q?fix(sdk/ime):=20IME=20=E4=BF=AE=E5=A4=8D(?= =?UTF-8?q?shift=E5=B0=8F=E5=86=99=E5=9F=BA/OK=E5=8F=AA=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E4=B8=8D=E5=85=B3/=E9=98=BB=E5=A1=9E=E5=96=82on=5Ftick=20agent?= =?UTF-8?q?-169)=20+=20dialog=20INPUT=E5=9B=9E=E5=BD=92(dlg=5Fime=5Fchange?= =?UTF-8?q?=5Fcb=E5=AE=9E=E6=97=B6=E5=9B=9E=E5=86=99+=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=96=B0=E8=AF=AD=E4=B9=89=20agent-173)=20+=20test=5Fsdk=5Ffix?= =?UTF-8?q?es=20shift=E6=96=AD=E8=A8=80=E6=9B=B4=E6=96=B0=20+=20=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E6=B5=8B=E8=AF=95test=5Fime=5Ffix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meow_ui.h | 6 +- meow_ui_dialog.c | 36 +++++++++ meow_ui_ime.c | 35 +++++--- meow_ui_input.c | 13 +++ test_ime_fix.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++ test_sdk_fixes.c | 77 +++++++++++++++++- 6 files changed, 359 insertions(+), 15 deletions(-) create mode 100644 test_ime_fix.c diff --git a/meow_ui.h b/meow_ui.h index 5813388..4c3da44 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -141,7 +141,8 @@ typedef struct meow_ime_ctx { void *ctx; } meow_ime_ctx; -/* 阻塞:弹出键盘直到确认/取消。返回 1=确认(buf 已回填),0=取消。 */ +/* 阻塞:弹出键盘直到关闭(ESC / 点背景取消);回车=只提交不关闭(内容经 on_change 实时同步)。 + * 关闭路径返回 0(取消);提交内容经 on_change 回调逐键同步,不依赖返回值回填 buf。 */ int meow_ui_text_input(meow_ime_ctx *ctx); /* 非阻塞:返回后 app 在 on_tick 里轮询 meow_ime_active()/meow_ime_text() */ @@ -443,7 +444,8 @@ int meow_btn_set_variant(meow_widget *w, int variant); /* 越界 clam meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, char *buf, int maxlen, meow_ime_mode mode); void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder); -/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);返回 1=确认 0=取消;buf 已回填 */ +/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);回车不关闭键盘,ESC/点背景关闭。 + * 内容经 on_change 实时同步到 w->buf(返回值关闭路径为 0,提交不依赖返回值回填)。 */ int meow_textfield_activate(meow_widget *w); const char *meow_textfield_text(meow_widget *w); diff --git a/meow_ui_dialog.c b/meow_ui_dialog.c index bca80c6..5d06425 100644 --- a/meow_ui_dialog.c +++ b/meow_ui_dialog.c @@ -64,6 +64,7 @@ struct meow_dialog { int done; meow_ime_ctx ime; /* INPUT 型(拷贝) */ int has_ime; + meow_ime_ctx *ime_orig; /* INPUT:App 传入的原始 ctx(回写最终文本,App 关闭后可读回) */ }; /* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */ @@ -162,6 +163,30 @@ static void dlg_repaint(meow_dialog *d, const meow_theme *th) meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh); } +/* M2.71-IME-fix:dialog INPUT 的 on_change——实时把 IME 文本回写进 d->ime.buf(dialog 内部拷贝), + * 并重绘卡片预览区,使用户边打字边看到当前文本。ESC/点背景(result=0)关闭后 d->ime.buf 仍是 + * 最新文本(否则 meow_ime_close(0) 不回写 buf → 文本丢失)。d->ime.buf 是 dialog 内部缓冲 + * (非 App 持有);App 的最终文本经 ime_orig 在关闭时回写(见 OK 按钮处理)。 */ +static void dlg_ime_change_cb(const char *text, void *ctx) +{ + meow_dialog *d = (meow_dialog *)ctx; + if (!d) return; + if (text) { + strncpy(d->ime.buf, text, sizeof d->ime.buf - 1); + d->ime.buf[sizeof d->ime.buf - 1] = 0; + } + const meow_theme *th = meow_theme_get(); + dlg_repaint(d, th); +} + +/* 准备好 dialog 的 IME 上下文:无论 App 传入(set_ime)或内部兜底(show 的默认 ctx), + * 都由 dialog 接管 on_change/ctx,以支持 dlg_ime_change_cb 的实时回写。 */ +static void dlg_prep_ime(meow_dialog *d) +{ + d->ime.on_change = dlg_ime_change_cb; + d->ime.ctx = d; +} + meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title) { meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d); @@ -183,7 +208,9 @@ void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime) { if (!d || !ime) return; d->ime = *ime; + d->ime_orig = ime; /* M2.71-IME-fix:记住 App 原始 ctx,关闭时回写最终文本 */ d->has_ime = 1; + dlg_prep_ime(d); /* dialog 接管 on_change:实时回写 d->ime.buf + 重绘卡片 */ } void meow_dialog_close(meow_dialog *d) @@ -199,6 +226,8 @@ void meow_dialog_show(meow_dialog *d) if (d->type == MEOW_DLG_INPUT && !d->has_ime) { memset(&d->ime, 0, sizeof d->ime); d->ime.maxlen = 255; + d->ime_orig = NULL; /* 无 App 原始 ctx,关闭时不回写 */ + dlg_prep_ime(d); } meow_input_start(); meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ @@ -236,6 +265,13 @@ void meow_dialog_show(meow_dialog *d) } else if (x >= cx && x < cx + g.btn_w) { /* OK */ if (d->type == MEOW_DLG_INPUT) { d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */ + /* M2.71-IME-fix:回车只提交不关(键盘仍开);文本经 on_change 已实时 + * 写进 d->ime.buf。这里无论 result=0/1 都把最终文本回写 App 原始 ctx, + * 保证 ESC/点背景(result=0)关闭后 App 仍能读到编辑结果(不再丢失)。 */ + if (d->ime_orig) { + strncpy(d->ime_orig->buf, d->ime.buf, sizeof d->ime_orig->buf - 1); + d->ime_orig->buf[sizeof d->ime_orig->buf - 1] = 0; + } } else { d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1; } diff --git a/meow_ui_ime.c b/meow_ui_ime.c index 87a5a8e..ce15065 100644 --- a/meow_ui_ime.c +++ b/meow_ui_ime.c @@ -1,11 +1,15 @@ /* * meow_ui_ime.c — 内置触屏软键盘(B1.6)。仅英文 QWERTY / 数字,ASCII。 * - * 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到确认(enter)/取消(esc|背景)。 + * 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到关闭(ESC / 点背景取消)。 + * 回车(K_ENTER)只提交(mark g_ime_result=1)不关闭,用户可继续输入。 + * 阻塞期间周期性喂 App on_tick(meow_ui_feed_tick),动态内容(进度条/时钟)不卡死。 * 非阻塞:meow_ui_text_input_async(ctx) 打开后返回;meow_ui_run 在 on_tick 间隙 * 随帧绘制并拦截触摸,app 用 meow_ime_active()/meow_ime_text() 轮询。 * * 键盘覆盖屏幕底部约 300px;点按输入;键位 44×36(touch target 达标)。 + * M2.71-IME-fix:字母键面存小写("qwertyuiop"…),shift 开→大写、关→小写; + * 数字/符号键不随 shift 转换。旧版键面存大写 → 无论 shift 开关恒输出大写(bug)。 */ #include "meow_ui.h" #include "meow_ui_internal.h" @@ -15,6 +19,11 @@ #include #include +/* 阻塞 IME 循环周期性喂 App on_tick(实现在 meow_ui_input.c,读该 TU 的 g_events)。 + * 让 IME 打开期间 App 的动态内容(进度条/时钟)继续刷新;调用在 meow_ime_draw 之前, + * 保证 App 重绘先进 back_buf、IME 面板随后盖上去(IME 始终最上,不被 App 重绘覆盖)。 */ +extern void meow_ui_feed_tick(void); + #define IME_MAXKEYS 96 #define KB_TOP 420 /* 面板顶(预览行起点) */ #define GW 44 /* 键宽 */ @@ -94,10 +103,10 @@ static void build_kb(meow_ime_mode mode) int y = KB_TOP + 50; if (mode == MEOW_IME_ASCII) { add_ch_row(y, "1234567890"); - add_ch_row(y + GCOL, "QWERTYUIOP"); + add_ch_row(y + GCOL, "qwertyuiop"); /* 第 3 行:9 字母 + SHIFT */ { - const char *r = "ASDFGHJKL"; + const char *r = "asdfghjkl"; int n = (int)strlen(r); int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -106,7 +115,7 @@ static void build_kb(meow_ime_mode mode) } /* 第 4 行:7 字母 + BACKSPACE */ { - const char *r = "ZXCVBNM"; + const char *r = "zxcvbnm"; int n = (int)strlen(r); int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -121,8 +130,9 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' '); add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>'); } - } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=提交并关闭)。 - * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 */ + } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=只提交不关闭)。 + * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 + * M2.71-IME-fix:回车不再关闭(与 ASCII 一致),关闭走 ESC/背景。 */ add_ch_row(y, "1234567890"); add_ch_row(y + GCOL, "-/.:*#+="); { @@ -132,7 +142,7 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); /* 空格 */ add_key(x + GCOL * 8, y + GCOL * 2, (GW + GAP) * 2 - GAP, GH, K_BACK, '<'); /* 退格 */ } - /* 回车(OK=提交并关闭) */ + /* 回车(OK=只提交不关闭) */ { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -148,7 +158,10 @@ static void apply_key(const struct kbkey *k) { switch (k->kind) { case K_CH: { - char c = g_shift && is_letter(k->ch) ? (char)toupper((unsigned char)k->ch) : k->ch; + /* M2.71-IME-fix:键面存小写(build_kb "qwertyuiop"…),shift 开→大写、关→小写; + * 数字/符号(非字母)不转换。旧版存大写 + else 分支恒返回大写 → 大小写不区分。 */ + char c = k->ch; + if (g_shift && is_letter(c)) c = (char)toupper((unsigned char)c); int len = (int)strlen(g_ime_buf); if (len < g_maxlen - 1) { g_ime_buf[len] = c; g_ime_buf[len + 1] = 0; @@ -169,7 +182,7 @@ static void apply_key(const struct kbkey *k) if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); } break; } - case K_ENTER: g_ime_result = 1; g_ime_done = 1; break; + case K_ENTER: g_ime_result = 1; break; /* M2.71-IME-fix:回车只提交(mark),不关键盘;关闭走 ESC/背景 */ case K_ESC: g_ime_result = 0; g_ime_done = 1; break; } } @@ -260,6 +273,10 @@ int meow_ui_text_input(meow_ime_ctx *ctx) meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b); } + /* M2.71-IME-fix:阻塞 IME 期间喂 App on_tick(动态内容继续刷,progress/时钟不卡死)。 + * 顺序要求:先喂 on_tick(App 重绘进 back_buf)→ 再 meow_ime_draw(IME 面板随后盖上去), + * 保证 IME 面板覆盖在最上,不被 App 的 d_paint_region 覆盖。 */ + meow_ui_feed_tick(); meow_ime_draw(); meow_ui_flush(); usleep(16000); diff --git a/meow_ui_input.c b/meow_ui_input.c index a7bbcb2..c19d3e2 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -445,3 +445,16 @@ void meow_ui_run(const meow_ui_events *ev) } } } + +/* M2.71-IME-fix:供 meow_ui_ime.c 的阻塞 IME 循环周期性喂 App on_tick。 + * 阻塞 IME 循环不经过 meow_ui_run,无法靠主循环喂 tick,故在此开独立入口; + * 与 meow_ui_run 共用 last_tick_ms(二者互斥运行,无竞争)。调用方在 meow_ime_draw 之前调用, + * 使 App 重绘先进 back_buf、IME 面板随后覆盖(IME 始终最上,不被 App 重绘盖住)。 */ +void meow_ui_feed_tick(void) +{ + if (!(g_events_set && g_events.on_tick)) return; + uint64_t now = now_ms(); + if (now - last_tick_ms < 16) return; + last_tick_ms = now; + g_events.on_tick(); +} diff --git a/test_ime_fix.c b/test_ime_fix.c new file mode 100644 index 0000000..ba170aa --- /dev/null +++ b/test_ime_fix.c @@ -0,0 +1,207 @@ +/* test_ime_fix.c — M2.71-IME-fix:宿主离屏验证三项修复(480x720,16bpp)。 + * ① shift 大小写正确:shift 关→小写('q'),开→大写('Q'); + * ② K_ENTER 只提交不关(meow_ime_active() 仍真),ESC 后关; + * ③ 阻塞 IME 循环喂 on_tick(动态内容重绘)且 IME 面板仍在最上 + * (On_tick 在 IME 面板区画红色哨兵行,IME 绘制后应盖回面板色,不被 App 覆盖)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include +#include + +/* ASCII 键盘键位(build_kb:KB_TOP=420 → 首行 y=470;GW=44,GAP=4,GCOL=48,行左缘 x=2): + * row2 "qwertyuiop" y∈[518,554);row3 "asdfghjkl"+SHIFT y∈[566,602); + * row5 ESC+SPACE+ENTER y∈[662,698)。 */ +#define K_Q_X 20 +#define K_Q_Y 536 +#define K_SHIFT_X 450 +#define K_SHIFT_Y 584 +#define K_ENTER_X 430 +#define K_ENTER_Y 680 +#define K_ESC_X 20 +#define K_ESC_Y 680 + +/* ---- 像素/配色辅助(离屏 back_buf) ---- */ +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + assert(bb && line > 0); + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } + +/* ---- ① 异步 shift 测试(直接 handle_tap,确定语义,无阻塞循环) ---- */ +static char g_echo[64] = ""; +static void ime_cb(const char *t, void *ctx) { (void)ctx; snprintf(g_echo, sizeof g_echo, "%s", t); } +static void test_shift(void) +{ + meow_ime_ctx c; memset(&c, 0, sizeof c); + c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb; + meow_ui_text_input_async(&c); + assert(meow_ime_active() == 1); + + meow_ime_handle_tap(K_Q_X, K_Q_Y); /* shift 关 → 'q' 小写 */ + assert(strcmp(meow_ime_text(), "q") == 0); + assert(strcmp(g_echo, "q") == 0); + assert(meow_ime_text()[0] >= 'a' && meow_ime_text()[0] <= 'z'); /* 明确小写 */ + + meow_ime_handle_tap(K_SHIFT_X, K_SHIFT_Y); /* shift 开 */ + meow_ime_handle_tap(K_Q_X, K_Q_Y); /* 'q' → 'Q' 大写 */ + + assert(strcmp(meow_ime_text(), "qQ") == 0); + assert(strcmp(g_echo, "qQ") == 0); + assert(meow_ime_text()[1] >= 'A' && meow_ime_text()[1] <= 'Z'); /* 明确大写 */ + printf("PASS shift: off='q'(lower), on='Q'(upper)\n"); fflush(stdout); + + meow_ime_handle_tap(K_ESC_X, K_ESC_Y); /* 关闭 */ + meow_ime_close(0); /* 复位(清 g_ime_done/active) */ + assert(meow_ime_active() == 0); +} + +/* ---- ②③ 阻塞 IME(经 meow_ui_run 驱动):K_ENTER 不关 / on_tick 喂 / IME 最上 ---- */ +static meow_widget *g_tf; +static char g_tfbuf[32] = ""; +static int g_tick = 0; +static int g_sentinel_painted = 0; +static int g_test_done = 0; + +static int g_active_after_enter = -1; /* K_ENTER 后 meow_ime_active()(期望 1) */ +static int g_active_after_esc = -1; /* ESC 后 meow_ime_active()(期望 0) */ +static int g_probe_r = -1, g_probe_g = -1, g_probe_b = -1; /* IME 面板区内取样(期望=面板色非哨兵红) */ +static char g_drv_text[64] = ""; /* shift 序列后 meow_ime_text()(期望 "qQ") */ + +/* 把 back_buf 写成 PPM(视觉目检 IME 面板渲染/最上;在 IME 空闲时调用,back_buf 稳定) */ +static void dump_ppm(const char *path) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + int W = meow_ui_width(), H = meow_ui_height(); + FILE *f = fopen(path, "wb"); + if (!f) return; + fprintf(f, "P6\n%d %d\n255\n", W, H); + for (int y = 0; y < H; y++) { + for (int x = 0; x < W; x++) { + int r, g, b; rd_px(x, y, &r, &g, &b); + fputc(r, f); fputc(g, f); fputc(b, f); + } + } + fclose(f); +} + +/* App on_tick:模拟 demo 动态重绘。IME 打开时先在 IME 面板区内画一条红色哨兵行, + * 再由 meow_ime_draw 盖回面板色 → 验证「喂 on_tick 后 IME 仍最上」的绘制顺序。 */ +static void host_tick(void) +{ + g_tick++; + if (meow_ime_active() && !g_sentinel_painted) { + g_sentinel_painted = 1; + meow_ui_fill_rect(0, 500, 480, 8, 0xFF0000); /* 哨兵行:App 内容拟 IME 面板区内 */ + meow_ui_flush_region(0, 500, 480, 8); + } + if (g_test_done) meow_ui_request_exit(); +} + +/* App on_tap:命中 TextField → 打开**阻塞** IME(同 hello demo 路径) */ +static void host_tap(int x, int y) +{ + (void)x; (void)y; + if (g_tf && meow_widget_hit(g_tf, x, y)) meow_textfield_activate(g_tf); +} + +static void *driver(void *arg) +{ + (void)arg; + usleep(600000); /* 等 meow_ui_run 起动(startup cooldown=500ms) */ + meow_input_inject(MEOW_EV_TAP, 150, 322); /* 点 TextField → 阻塞 IME */ + usleep(160000); + meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 关 → 'q' */ + usleep(90000); + meow_input_inject(MEOW_EV_TAP, K_SHIFT_X, K_SHIFT_Y); /* shift 开 */ + usleep(60000); + meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 开 → 'Q' → 缓冲 "qQ" */ + usleep(90000); + strncpy(g_drv_text, meow_ime_text(), sizeof g_drv_text - 1); + g_drv_text[sizeof g_drv_text - 1] = 0; + + meow_input_inject(MEOW_EV_TAP, K_ENTER_X, K_ENTER_Y); /* K_ENTER:只提交不关 */ + usleep(160000); + g_active_after_enter = meow_ime_active(); /* 期望 1(键盘仍开) */ + + rd_px(240, 500, &g_probe_r, &g_probe_g, &g_probe_b); /* IME 面板区内取样 */ + dump_ppm("/tmp/ime_panel.ppm"); /* 目检:IME 面板渲染 + 盖住哨兵 */ + printf("[drv] text=%s active_after_enter=%d probe=%02X%02X%02X sentineld=%d\n", + g_drv_text, g_active_after_enter, g_probe_r, g_probe_g, g_probe_b, + g_sentinel_painted); fflush(stdout); + + meow_input_inject(MEOW_EV_TAP, K_ESC_X, K_ESC_Y); /* ESC → 关闭 */ + usleep(160000); + g_active_after_esc = meow_ime_active(); /* 期望 0 */ + g_test_done = 1; + return NULL; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/ime_fix.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + const meow_theme *th = meow_theme_get(); + + test_shift(); /* ① */ + + /* ②③ 在 meow_ui_run 里驱动(g_events 仅 meow_ui_run 能设置) */ + g_tf = meow_textfield_create(10, 300, 300, 44, "Type here...", g_tfbuf, 32, MEOW_IME_ASCII); + meow_ui_events ev; memset(&ev, 0, sizeof ev); + ev.on_tap = host_tap; + ev.on_tick = host_tick; + pthread_t thd; pthread_create(&thd, NULL, driver, NULL); + meow_ui_run(&ev); + pthread_join(thd, NULL); + + /* 断言 ② */ + printf("[assert] active_after_enter=%d (expect 1), active_after_esc=%d (expect 0)\n", + g_active_after_enter, g_active_after_esc); + assert(g_active_after_enter == 1); /* K_ENTER 不关 */ + assert(g_active_after_esc == 0); /* ESC 关 */ + + /* 断言 ①(阻塞路径经 on_change 实时同步到字段) */ + printf("[assert] blocking text=%s (expect \"qQ\"), tf=%s (expect \"qQ\")\n", + g_drv_text, meow_textfield_text(g_tf)); + assert(strcmp(g_drv_text, "qQ") == 0); + assert(strcmp(meow_textfield_text(g_tf), "qQ") == 0); + + /* 断言 ③:on_tick 在 IME 打开期间被喂过(哨兵已画)+ IME 面板盖住哨兵 */ + int pr = (th->surfaceContainerHighest >> 16) & 0xFF; + int pg = (th->surfaceContainerHighest >> 8) & 0xFF; + int pb = (th->surfaceContainerHighest) & 0xFF; + printf("[assert] sentinel_painted=%d probe=%02X%02X%02X panel=%02X%02X%02X\n", + g_sentinel_painted, g_probe_r, g_probe_g, g_probe_b, pr, pg, pb); + assert(g_sentinel_painted == 1); /* on_tick 在 IME 期间被调(重绘过) */ + assert(g_tick > 0); + assert(near(g_probe_r, pr) && near(g_probe_g, pg) && near(g_probe_b, pb)); /* IME 面板最上 */ + /* 哨兵红 0xFF0000 与面板色显著不同,确保上面断言有判别力(面板色不应近纯红) */ + assert(!(near(pr, 0xFF) && near(pg, 0x00) && near(pb, 0x00))); + + printf("ALL PASS\n"); + meow_ui_shutdown(); + return 0; +} diff --git a/test_sdk_fixes.c b/test_sdk_fixes.c index bd7c42b..e2a3401 100644 --- a/test_sdk_fixes.c +++ b/test_sdk_fixes.c @@ -3,7 +3,8 @@ * bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗); * bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提); * bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影); - * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。 + * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原; + * fix1 dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义:回车只提交不关)。 * * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ @@ -39,6 +40,7 @@ static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12 /* ---- 为阻塞模态/IME 在后台注入事件 ---- */ static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0; +static int K_DLG_OK_X = 0, K_DLG_OK_Y = 0; /* dialog INPUT 的 OK 按钮中心(运行时算) */ static void *inject_thread(void *arg) { (void)arg; @@ -122,10 +124,14 @@ static void test_ime_echo_and_restore(void) /* --- 异步:验『回显/输入框实时同步(on_change 随键入触发)』--- */ meow_ui_text_input_async(&c); assert(meow_ime_active() == 1); - meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 第3行第1键 = 'A'(shift 关 → 缓冲 'A') */ + meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 行3第1键 = 'a'(shift 关 → 小写 'a') */ meow_ime_handle_tap(20, 580); - assert(strcmp(g_echo, "A") == 0 && strcmp(meow_ime_text(), "A") == 0); - printf("[bug7] ime echo live-sync after 'A' -> \"%s\" (on_change fired) PASS\n", g_echo); fflush(stdout); + assert(strcmp(g_echo, "a") == 0 && strcmp(meow_ime_text(), "a") == 0); + printf("[bug7] ime echo live-sync after 'a' -> \"%s\" (shift-off lower) PASS\n", g_echo); fflush(stdout); + meow_ime_handle_tap(450, 584); /* SHIFT 开 */ + meow_ime_handle_tap(20, 580); /* 'a' → 'A' 大写 */ + assert(strcmp(g_echo, "aA") == 0 && strcmp(meow_ime_text(), "aA") == 0); + printf("[bug7] shift-on -> 'A' (echo \"%s\") PASS\n", g_echo); fflush(stdout); meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */ meow_ime_handle_tap(20, 680); assert(meow_ime_active() == 0); @@ -143,6 +149,68 @@ static void test_ime_echo_and_restore(void) printf("PASS\n"); } +/* ---- 回归①:dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义)。 + * 复现 dialog.c dlg_input_ok_center → 复用 dialog 的 OK 按钮中心几何(与 dlg_calc 同一公式)。 */ +static void dialog_input_ok_center(int *ox, int *oy) +{ + const meow_theme *th = meow_theme_get(); + int W = meow_ui_width(), H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24, btn = 44; + int tH = meow_ui_text_height(th->font_title); + int bH = meow_ui_text_height(th->font_body); + int content = bH + 8; /* INPUT(无 items)→ 1 行正文预览 */ + int dw = 348; if (dw > W - 40) dw = W - 40; + int dx = (W - dw) / 2; + int dh = pad + tH + 12 + content + pad + (16 + btn); + int avail = H - sb - 20; + if (dh > avail) dh = avail; + int dy = (H - dh) / 2; if (dy < sb) dy = sb; + int row_y = dy + pad + tH + 12; + int btn_y = row_y + content + 16; + int btn_w = (dw - 2 * pad - 12) / 2; + int cx = dx + pad + btn_w + 12; /* OK = 右侧填充按钮左缘 */ + *ox = cx + btn_w / 2; + *oy = btn_y + btn / 2; +} + +static void *dlg_input_driver(void *arg) +{ + (void)arg; + usleep(40000); /* 等 dialog show 起(遮罩+卡片已绘) */ + meow_input_inject(MEOW_EV_TAP, K_DLG_OK_X, K_DLG_OK_Y); /* 点 OK → 打开阻塞 IME */ + usleep(140000); + meow_input_inject(MEOW_EV_TAP, 20, 584); /* 行3第1键 'a'(shift 关) */ + usleep(140000); + meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC → 关 IME → dialog 随之关 */ + return NULL; +} + +static void test_dialog_input_preserve(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_ime_ctx ime; memset(&ime, 0, sizeof ime); + ime.mode = MEOW_IME_ASCII; ime.maxlen = 64; + strcpy(ime.buf, ""); + meow_dialog *d = meow_dialog_create(MEOW_DLG_INPUT, "Name"); + meow_dialog_set_ime(d, &ime); + + dialog_input_ok_center(&K_DLG_OK_X, &K_DLG_OK_Y); + pthread_t thd; pthread_create(&thd, NULL, dlg_input_driver, NULL); + meow_dialog_show(d); + pthread_join(thd, NULL); + + /* ESC 关闭(result=0)→ 文本应保留:App 原始 ctx.buf 应含 'a'(不再丢失),且是取消语义 */ + printf("[fix1] dialog INPUT after ESC: result=%d App.ime.buf=\"%s\" (expect 0, \"a\")\n", + meow_dialog_result(d), ime.buf); + assert(meow_dialog_result(d) == 0); + assert(strcmp(ime.buf, "a") == 0); + printf("[fix1] dialog INPUT text preserved on ESC PASS\n"); + meow_dialog_free(d); +} + int main(void) { setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1); @@ -152,6 +220,7 @@ int main(void) test_circular_extent(); test_modal_restore(); test_ime_echo_and_restore(); + test_dialog_input_preserve(); meow_ui_shutdown(); printf("ALL PASS\n"); return 0; From fa268c0fce92a385800692b5d5fd60ab003d8f7b Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 02:38:32 +0800 Subject: [PATCH 22/49] =?UTF-8?q?feat(sdk/view):=20=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=89=B9=E6=AC=A1=E2=91=A0=20=E2=80=94=20z?= =?UTF-8?q?=E5=BA=8F=E7=BB=98=E5=88=B6=E7=89=A9=E6=B3=A8=E5=86=8C=E8=A1=A8?= =?UTF-8?q?(meow=5Fui=5Fdraw.c:=20=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C+aut?= =?UTF-8?q?o=5Fz(=E6=B7=B1=E5=BA=A6*=E6=AD=A5=E9=95=BF+=E5=B1=82=E5=86=85s?= =?UTF-8?q?eq)+set=5Fz(999)=E7=BD=AE=E9=A1=B6+MEOW=5FZ=5FAUTO=5FMAX=3D127)?= =?UTF-8?q?=20+=20meow=5Fview=E9=AA=A8=E6=9E=B6(meow=5Fui=5Fview.c/.h:=20?= =?UTF-8?q?=E5=B5=8C=E5=A5=97=E5=AE=B9=E5=99=A8+=E5=85=AD=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F+page=5Fpush/pop/replace=E5=8C=85?= =?UTF-8?q?=E8=A3=85activity=E6=A0=88+sub=5Fpush/pop=E9=A2=84=E7=95=99?= =?UTF-8?q?=E6=A1=A9)=20+=20=E6=94=B6=E6=95=9Blive=5Fbtn=E8=B5=B0invalidat?= =?UTF-8?q?e=20+=20hello=E8=BF=81=E7=A7=BB(d=5Fpaint=5Fregion/d=5Fredraw?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E2=86=92meow=5Fui=5Finvalidate,=20chrome=20s?= =?UTF-8?q?et=5Fz(999),=20demo=E6=96=87=E6=9C=AC=E6=B3=A8=E5=86=8CCUSTOM?= =?UTF-8?q?=20z=3D128)=20(agent-176,=20=E5=AE=9A=E6=A1=88=E8=A7=81SDK=5FVI?= =?UTF-8?q?EW=5FFRAMEWORK=5FDESIGN.md=20=C2=A7E=E2=91=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a d814d0ed→7e20f10e; hello 0a1c96a5→2ce09466 宿主验证: test_p1p2p3_repro P1c/P2/P3 ALL PASS; test_meow_ui_widgets ALL PASS 无回归; mipsel -Wall -Wextra 0 warning --- build_sdk.sh | 2 +- meow_ui.h | 52 ++++++ meow_ui_draw.c | 357 +++++++++++++++++++++++++++++++++++++++++ meow_ui_internal.h | 25 +++ meow_ui_view.c | 197 +++++++++++++++++++++++ meow_ui_view.h | 98 +++++++++++ meow_ui_widget.c | 48 +++++- test_ime_fix.c | 1 + test_meow_ui_widgets.c | 1 + test_p1p2p3_repro.c | 34 ++-- test_sdk_fixes.c | 1 + 11 files changed, 799 insertions(+), 17 deletions(-) create mode 100644 meow_ui_draw.c create mode 100644 meow_ui_view.c create mode 100644 meow_ui_view.h diff --git a/build_sdk.sh b/build_sdk.sh index 9994d4d..b7440e4 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index 4c3da44..c1df222 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -210,6 +210,58 @@ void meow_ui_global_remove(int id); /* ③ 系统级控件:状态栏/通知中心/左缘返回滑条/电源菜单等全局叠加层——仅 SDK 内部接口, * 不向用户 App 开放任何公共悬浮/叠加层 API。既有状态栏/返回滑条逻辑(§3.1)不动。 */ +/* ============================================================ */ +/* §6.2 绘制物注册表 / z 序 / 区域重画(合并 SDK_Z_ORDER_DESIGN,源:2026-08-31) + * ------------------------------------------------------------ + * 非保留模型(§5.8.7)下,把「清底 + 按 z 重画重叠区完整 z 栈」下沉到 SDK,让所有 App + * 自动获得正确的局部重画(根治 P1:下层动态控件局部重绘会把重叠区上层 chrome 抹掉)。 + * - 控件(meow_widget)创建即自动注册进绘制物注册表;rect 实时读 w->x/y/w/h; + * - App 直绘的非控件内容(meow_ui_text/fill_rect 等)须显式 meow_draw_custom_register + * 注册(声明 bbox + 绘制回调),否则区域重画会漏画(非保留模型固有代价); + * - z 自动推导(子>父、同层注册先序);显式 meow_widget_set_z(999/MEOW_Z_TOP) 置顶(粘滞)。 + * 用法:meow_ui_invalidate(x,y,w,h) 局部重画(清底+按 z 升序重画所有相交绘制物+上屏), + * 不整帧;meow_ui_paint_full() 整帧按 z 序重画。默认底=meow_theme bg(set_page_bg 覆盖)。 + * 兼容:新 API 全部 opt-in;不调用本节的既有 App(widget/activity/flush 均不动)行为完全不变。 */ +/* ============================================================ */ + +/* 绘制物种类:控件(自动注册)/ App 直绘内容(显式注册)。 */ +typedef enum { MEOW_ITEM_WIDGET = 0, MEOW_ITEM_CUSTOM = 1 } meow_draw_kind; +typedef void (*meow_draw_fn)(void *ctx); /* CUSTOM 绘制回调(ctx 由 App 持有) */ + +/* ---- 层级常量(保证"子>父" + 值域远小于置顶值)---- */ +#define MEOW_Z_STRIDE 16 /* 每层容器嵌套的 z 步进(子层至少 +16) */ +#define MEOW_Z_DEPTH_MAX 7 /* 自动推导最大嵌套深度(0..6,>7 折叠) */ +#define MEOW_Z_PER_LEVEL_MAX 15 /* 每层最多 16 槽(seq 0..15,超出折叠按注册序) */ +#define MEOW_Z_AUTO_MAX (MEOW_Z_STRIDE * MEOW_Z_DEPTH_MAX + MEOW_Z_PER_LEVEL_MAX) /* 127 */ +#define MEOW_Z_TOP 1000 /* 惯例"置顶"值;≥(MEOW_Z_AUTO_MAX+1)=128 即超一切自动值 */ + +/* 控件显式置层:z 为任意非负整数(0..INT_MAX,无上限),越大越上;z>所有其它控件即恒在最上。 + * 惯例设 999(或 MEOW_Z_TOP)即"置顶"。显式 z 存于绘制注册表(按 w 反查 entry,不改 + * struct meow_widget——零 ABI 破坏)且**粘滞**:不被后续自动推导覆盖;clear_z 才回退。 */ +void meow_widget_set_z(meow_widget *w, int z); +int meow_widget_z(const meow_widget *w); /* 当前生效 z(显式=其值,否则=自动推导) */ +void meow_widget_clear_z(meow_widget *w); /* 清除显式 z,回退自动推导 */ + +/* ---- 非控件直绘内容注册为绘制物(CUSTOM)---- + * App 声明 bbox + 绘制回调 + 显式 z(任意非负;999=置顶);区域重画/整帧重画会画到它。 + * 返回 id(供 meow_draw_unregister / meow_draw_set_z);注册表满返回 0。 + * 生命周期契约:App 持有 ctx;widget 类内容不必注册(自动);离开页面前 unregister。 */ +int meow_draw_custom_register(int z, int x, int y, int w, int h, + meow_draw_fn cb, void *ctx); +void meow_draw_unregister(int id); /* 注销(id 失效,勿再用) */ +int meow_draw_set_z(int id, int z); /* 改 custom 显式 z;无效 id 返回 -1 */ + +/* 页面背景色(区域重画/整帧清底用);默认 meow_theme bg。 */ +void meow_ui_set_page_bg(uint32_t color); +uint32_t meow_ui_page_bg(void); + +/* 声明某区域内容已变化 → 清底(page_bg) + 按 z 升序重画所有相交绘制物 + 上屏。 + * 局部(只画相交绘制物,完整 bbox——见 SDK_Z_ORDER_DESIGN §5),不整帧、不冲状态栏。 */ +void meow_ui_invalidate(int x, int y, int w, int h); + +/* 整帧按 z 序重画全部(set_page_bg 清底 + 全注册表按 z 升序 + 上屏)。 */ +void meow_ui_paint_full(void); + /* ============================================================ */ /* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */ /* ============================================================ */ diff --git a/meow_ui_draw.c b/meow_ui_draw.c new file mode 100644 index 0000000..fc25145 --- /dev/null +++ b/meow_ui_draw.c @@ -0,0 +1,357 @@ +/* + * meow_ui_draw.c — 绘制物注册表 / z 序 / 区域重画(§6.2 / SDK_Z_ORDER_DESIGN §2)。 + * + * 非保留模型下,把「清底 + 按 z 重画重叠区完整 z 栈」下沉到 SDK: + * - 控件(meow_widget)创建即自动注册(create 末尾调 meow_draw_widget_register), + * meow_widget_free 自动注销;rect 实时读 w->x/y/w/h; + * - App 直绘的非控件内容显式 meow_draw_custom_register(声明 bbox + 绘制回调); + * - z 自动推导(子>父、同层注册先序);显式 set_z 粘滞、置顶。 + * 显式 z 存于本注册表(按 w 反查 entry),**不改 struct meow_widget**(零 ABI 破坏)。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_view.h" +#include +#include + +#define MEOW_DRAW_MAX 256 +#define MEOW_ITEM_VIEW 2 /* 内部 kinds(不进公共枚举):meow_view 绘制物 */ + +typedef struct draw_entry { + int kind; /* MEOW_ITEM_WIDGET / MEOW_ITEM_CUSTOM / MEOW_ITEM_VIEW */ + meow_widget *w; /* WIDGET:绘制其 meow_widget_draw(w) */ + meow_draw_fn draw_cb; /* CUSTOM:绘制回调 */ + void *ctx; /* CUSTOM: 传给 draw_cb; VIEW: (meow_view*)v */ + int rx, ry, rw, rh; /* CUSTOM bbox(WIDGET 读 w->x/y/w/h,VIEW 读 v->x/y/w/h) */ + int z; /* 生效 z(显式=其值,否则=自动推导) */ + int z_explicit; /* 1=显式(粘滞) */ + int id; /* CUSTOM id(0=无效) */ + int seq; /* 全局注册序(z 同值时稳定先序) */ + struct meow_view *view; /* WIDGET:所属 view(z 深度源)+1 */ +} draw_entry; + +static draw_entry g_e[MEOW_DRAW_MAX]; +static int g_n = 0; +static int g_seq = 0; /* 全局注册序(单调递增) */ +static int g_next_id = 1; /* CUSTOM id 起点(0=无效) */ + +static uint32_t g_page_bg = 0; +static int g_page_bg_set = 0; + +/* ---- 内部访问器(widget.c 提供;跨 TU 用 meow_ui_internal.h 声明)---- */ +static int w_is_layout(const meow_widget *w) { return meow_widget_is_layout(w); } + +/* ---- 查找 ---- */ +static draw_entry *e_find_widget(const meow_widget *w) +{ + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_WIDGET && g_e[i].w == w) return &g_e[i]; + return NULL; +} +static draw_entry *e_find_view(const meow_view *v) +{ + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_VIEW && g_e[i].ctx == (void *)v) return &g_e[i]; + return NULL; +} +static draw_entry *e_find_id(int id) +{ + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_CUSTOM && g_e[i].id == id) return &g_e[i]; + return NULL; +} + +/* ---- 深度:view 树深度 ---- */ +static int view_depth(const meow_view *v) +{ + int d = 0; + const meow_view *p = v ? v->parent : NULL; + while (p) { + d++; + if (d > MEOW_Z_DEPTH_MAX) break; + p = p->parent; + } + return d; +} + +/* ---- 深度:widget 所属嵌套深度(view 树为主;否则 W_LAYOUT children 反推;否则 0)---- */ +static int widget_nest_depth(meow_widget *w, int depth) +{ + if (depth > MEOW_Z_DEPTH_MAX) return MEOW_Z_DEPTH_MAX; + draw_entry *e = e_find_widget(w); + if (e && e->view) return view_depth(e->view) + 1; /* 视图树深度源(更确定) */ + /* 注册表反推:w 是否经 meow_layout_add 挂在某布局下(此时布局自身已注册为 WIDGET)。 */ + for (int i = 0; i < g_n; i++) { + if (g_e[i].kind != MEOW_ITEM_WIDGET) continue; + meow_widget *lw = g_e[i].w; + if (!lw || !w_is_layout(lw)) continue; + int nc = meow_widget_nchild(lw); + for (int j = 0; j < nc; j++) + if (meow_widget_child_at(lw, j) == w) + return widget_nest_depth(lw, depth + 1) + 1; /* 子层 >= 父层+1 */ + } + return 0; +} + +/* 计算 auto z(显式不重算——粘滞):depth*STRIDE + 层内 seq(注册先序,折叠 MEOW_Z_PER_LEVEL_MAX)。 */ +static void e_compute_auto(draw_entry *e) +{ + if (e->z_explicit) return; + int depth = 0; + if (e->kind == MEOW_ITEM_WIDGET && e->w) depth = widget_nest_depth(e->w, 0); + else if (e->kind == MEOW_ITEM_VIEW && e->ctx) depth = view_depth((const meow_view *)e->ctx); + /* CUSTOM:z 恒显式(register 传入),不进 auto 分支 */ + int cnt = 0; + for (int i = 0; i < g_n; i++) { + draw_entry *o = &g_e[i]; + if (o == e || o->z_explicit) continue; + if (o->seq >= e->seq) continue; + if (o->z / MEOW_Z_STRIDE == depth) cnt++; + } + if (cnt > MEOW_Z_PER_LEVEL_MAX) cnt = MEOW_Z_PER_LEVEL_MAX; + e->z = depth * MEOW_Z_STRIDE + cnt; +} + +/* ============================================================ */ +/* 控件自动注册 / 注销(create 末尾 / meow_widget_free 调用) */ +/* ============================================================ */ +void meow_draw_widget_register(meow_widget *w) +{ + if (!w || e_find_widget(w)) return; /* 幂等 */ + if (g_n >= MEOW_DRAW_MAX) return; /* 满:静默(骨架级) */ + draw_entry *e = &g_e[g_n++]; + memset(e, 0, sizeof *e); + e->kind = MEOW_ITEM_WIDGET; + e->w = w; + e->seq = g_seq++; + e_compute_auto(e); +} + +void meow_draw_widget_unregister(meow_widget *w) +{ + if (!w) return; + int idx = -1; + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_WIDGET && g_e[i].w == w) { idx = i; break; } + if (idx < 0) return; + g_e[idx] = g_e[--g_n]; /* 交换移除(seq 保留,排序稳定) */ +} + +int meow_draw_widget_registered(const meow_widget *w) +{ + return w ? e_find_widget(w) != NULL : 0; +} + +void meow_draw_widget_set_view(meow_widget *w, struct meow_view *v) +{ + draw_entry *e = e_find_widget(w); + if (!e) return; + e->view = v; + e_compute_auto(e); +} + +/* ============================================================ */ +/* 显式 z(widget) */ +/* ============================================================ */ +void meow_widget_set_z(meow_widget *w, int z) +{ + if (!w || z < 0) return; + draw_entry *e = e_find_widget(w); + if (!e) return; + e->z_explicit = 1; + e->z = z; +} +int meow_widget_z(const meow_widget *w) +{ + if (!w) return 0; + draw_entry *e = e_find_widget((meow_widget *)w); + if (!e) return 0; + if (e->z_explicit) return e->z; + e_compute_auto(e); + return e->z; +} +void meow_widget_clear_z(meow_widget *w) +{ + if (!w) return; + draw_entry *e = e_find_widget(w); + if (!e) return; + e->z_explicit = 0; + e_compute_auto(e); +} + +/* ============================================================ */ +/* CUSTOM(App 直绘)注册 / 注销 / 改 z */ +/* ============================================================ */ +int meow_draw_custom_register(int z, int x, int y, int w, int h, + meow_draw_fn cb, void *ctx) +{ + if (!cb || w <= 0 || h <= 0) return 0; + if (g_n >= MEOW_DRAW_MAX) return 0; + draw_entry *e = &g_e[g_n++]; + memset(e, 0, sizeof *e); + e->kind = MEOW_ITEM_CUSTOM; + e->draw_cb = cb; + e->ctx = ctx; + e->rx = x; e->ry = y; e->rw = w; e->rh = h; + e->z = z < 0 ? 0 : z; + e->z_explicit = 1; + e->id = g_next_id++; + e->seq = g_seq++; + return e->id; +} +void meow_draw_unregister(int id) +{ + if (id <= 0) return; + int idx = -1; + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_CUSTOM && g_e[i].id == id) { idx = i; break; } + if (idx < 0) return; + g_e[idx] = g_e[--g_n]; +} +int meow_draw_set_z(int id, int z) +{ + if (id <= 0 || z < 0) return -1; + draw_entry *e = e_find_id(id); + if (!e) return -1; + e->z = z; + e->z_explicit = 1; + return 0; +} + +/* ============================================================ */ +/* view 绘制物注册 / 注销 */ +/* ============================================================ */ +void meow_draw_view_register(struct meow_view *v) +{ + if (!v || e_find_view(v)) return; + if (g_n >= MEOW_DRAW_MAX) return; + draw_entry *e = &g_e[g_n++]; + memset(e, 0, sizeof *e); + e->kind = MEOW_ITEM_VIEW; + e->ctx = (void *)v; + e->seq = g_seq++; + e_compute_auto(e); +} +void meow_draw_view_unregister(struct meow_view *v) +{ + if (!v) return; + int idx = -1; + for (int i = 0; i < g_n; i++) + if (g_e[i].kind == MEOW_ITEM_VIEW && g_e[i].ctx == (void *)v) { idx = i; break; } + if (idx < 0) return; + g_e[idx] = g_e[--g_n]; +} +int meow_draw_view_entered(const struct meow_view *v) +{ + return v ? meow_view_get_state(v) & MEOW_VIEW_STATE_ENTERED : 0; +} +void meow_draw_view_set_z(struct meow_view *v, int z) +{ + draw_entry *e = e_find_view(v); + if (!e) return; + if (z <= 0) { + e->z_explicit = 0; + e_compute_auto(e); + } else { + e->z_explicit = 1; + e->z = z; + } +} + +/* ============================================================ */ +/* page bg / 区域重画 / 整帧 */ +/* ============================================================ */ +void meow_ui_set_page_bg(uint32_t color) { g_page_bg = color; g_page_bg_set = 1; } +uint32_t meow_ui_page_bg(void) { return g_page_bg_set ? g_page_bg : meow_theme_get()->bg; } + +static int rect_hit(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) +{ + return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; +} + +static int entry_rect(draw_entry *e, int out[4]) +{ + if (e->kind == MEOW_ITEM_WIDGET && e->w) { + meow_widget_rect(e->w, out); + } else if (e->kind == MEOW_ITEM_VIEW && e->ctx) { + meow_view *v = (meow_view *)e->ctx; + out[0] = v->x; out[1] = v->y; out[2] = v->w; out[3] = v->h; + } else { + out[0] = e->rx; out[1] = e->ry; out[2] = e->rw; out[3] = e->rh; + } + return out[2] > 0 && out[3] > 0; +} + +static void draw_one(draw_entry *e) +{ + switch (e->kind) { + case MEOW_ITEM_WIDGET: + meow_widget_draw(e->w); + break; + case MEOW_ITEM_CUSTOM: + e->draw_cb(e->ctx); + break; + case MEOW_ITEM_VIEW: { + meow_view *v = (meow_view *)e->ctx; + if (v->hooks.on_draw) v->hooks.on_draw(v, meow_theme_get(), v->ctx); + break; + } + default: + break; + } +} + +static void paint(int full, int x, int y, int w, int h) +{ + uint32_t bg = meow_ui_page_bg(); + if (full) { + meow_ui_clear(bg); + } else { + if (w <= 0 || h <= 0) return; + meow_ui_fill_rect(x, y, w, h, bg); + } + /* 刷新 auto z + 组装按 (z,seq) 升序的绘制序(owner==active 过滤 WIDGET;view 须已进入)。 */ + draw_entry *order[MEOW_DRAW_MAX]; + int nn = 0; + int active = meow_ui_activity_active(); + for (int i = 0; i < g_n; i++) { + draw_entry *e = &g_e[i]; + e_compute_auto(e); + if (e->kind == MEOW_ITEM_WIDGET && e->w && meow_widget_owner(e->w) != active) continue; + if (e->kind == MEOW_ITEM_VIEW && !meow_draw_view_entered((meow_view *)e->ctx)) continue; + order[nn++] = e; + } + /* 稳定插入排序:(z 升序,同 z 按 seq 升序=注册先序)。 */ + for (int i = 1; i < nn; i++) { + draw_entry *key = order[i]; + int j = i - 1; + while (j >= 0 && (order[j]->z > key->z || + (order[j]->z == key->z && order[j]->seq > key->seq))) { + order[j + 1] = order[j]; + j--; + } + order[j + 1] = key; + } + if (full) { + for (int i = 0; i < nn; i++) draw_one(order[i]); + meow_ui_flush(); + } else { + int r[4]; + for (int i = 0; i < nn; i++) { + if (!entry_rect(order[i], r)) continue; + if (rect_hit(r[0], r[1], r[2], r[3], x, y, w, h)) draw_one(order[i]); + } + meow_ui_flush_region(x, y, w, h); + } +} + +void meow_ui_invalidate(int x, int y, int w, int h) +{ + if (w <= 0 || h <= 0) return; + paint(0, x, y, w, h); +} + +void meow_ui_paint_full(void) +{ + paint(1, 0, 0, 0, 0); +} diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 62dbf3c..bc3c89b 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -184,6 +184,31 @@ void meow_widget_touch_refresh(void); void meow_ui_global_render(void); int meow_ui_global_hit(int x, int y); +/* ---- 绘制物注册表(meow_ui_draw.c,§6.2 绘制物注册表/z序/区域重画)---- + * 内部协作接口:widget.c 在 create 末尾自动注册、meow_widget_free 自动注销;view.c 在 + * meow_view_add_widget/meow_view_free 挂/摘 widget 与 view 的 z 深度源;live_btn 收敛查询 + * 是否已注册(已注册→走 invalidate 区域重画,否则回退单控件 flush)。 */ +struct meow_view; /* 仅前向声明;完整类型在 meow_ui_view.h */ + +void meow_draw_widget_register(struct meow_widget *w); /* create 末尾:自动注册(幂等) */ +void meow_draw_widget_unregister(struct meow_widget *w); /* meow_widget_free:自动注销 */ +int meow_draw_widget_registered(const struct meow_widget *w);/* 是否在注册表(live_btn 收敛用) */ +void meow_draw_widget_set_view(struct meow_widget *w, struct meow_view *v); /* 挂 view:重算 auto z */ +void meow_draw_view_register(struct meow_view *v); /* view create:注册绘制物(on_draw 用) */ +void meow_draw_view_unregister(struct meow_view *v); /* view free:注销 */ +int meow_draw_view_entered(const struct meow_view *v); /* view 是否已进入(绘制过滤参考) */ +void meow_draw_view_set_z(struct meow_view *v, int z); /* view 显式 z(z<=0=清除回退自动) */ + +/* 控件布局深度反推(meow_ui_draw.c 用;widget.c 实现,读 struct meow_widget 内部字段)。 + * 让绘制注册表在「无 meow_view 时」仍能从 W_LAYOUT children 反推子>父深度。 */ +int meow_widget_is_layout(const struct meow_widget *w); /* 1 = W_LAYOUT */ +int meow_widget_nchild(const struct meow_widget *w); /* 布局子项数 */ +const struct meow_widget *meow_widget_child_at(const struct meow_widget *w, int idx); + +/* 控件几何/归属访问器(meow_ui_draw.c 用:绘制物 rect 实时读 + owner 过滤)。 */ +int meow_widget_owner(const struct meow_widget *w); /* 创建时绑定的 Activity */ +void meow_widget_rect(const struct meow_widget *w, int out[4]); /* x,y,w,h */ + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/meow_ui_view.c b/meow_ui_view.c new file mode 100644 index 0000000..9ab349d --- /dev/null +++ b/meow_ui_view.c @@ -0,0 +1,197 @@ +/* + * meow_ui_view.c — meow_view 视图/页面框架骨架(§A 视图数据结构/嵌套/生命周期/页面导航)。 + * + * meow_view 是**薄壳**:容器 + 生命周期 + 事件路由;叶子仍是 meow_widget(不改)。绘制走 + * z 序绘制物注册表(on_draw 回调,非保留——不缓存像素)。opt-in:不调 meow_view_* 的 App + * 完全照旧(widget/activity/flush 不动)。页面模型 = 定案 b(顶层页切走 activity 栈, + * page_push/pop/replace 包装;不做页内子导航栈,sub_push/pop 为预留扩展位 a 桩)。 + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include "meow_ui_view.h" +#include +#include + +#define MEOW_VIEW_BY_ID_MAX 32 + +static meow_view *g_view_by_id[MEOW_VIEW_BY_ID_MAX]; /* id → 顶层页 view(page_push/pop 查找) */ +static meow_view *g_top_view = NULL; /* 当前进入的顶层页 view(生命周期跟踪) */ + +static void run_fn(meow_view_fn f, meow_view *v) +{ + if (f) f(v, v->ctx); +} + +/* 登记/清除 id→view 映射(幂等)。 */ +static void view_map_set(meow_view *v) +{ + if (!v) return; + int id = v->id; + if (id >= 0 && id < MEOW_VIEW_BY_ID_MAX) g_view_by_id[id] = v; +} +static void view_map_clear(meow_view *v) +{ + if (!v) return; + int id = v->id; + if (id >= 0 && id < MEOW_VIEW_BY_ID_MAX && g_view_by_id[id] == v) g_view_by_id[id] = NULL; +} +static meow_view *view_map_get(int id) +{ + return (id >= 0 && id < MEOW_VIEW_BY_ID_MAX) ? g_view_by_id[id] : NULL; +} + +/* 进入一个 view(置 ENTERED + on_enter)。 */ +static void view_enter(meow_view *v) +{ + if (!v || (v->state & MEOW_VIEW_STATE_ENTERED)) return; + v->state |= MEOW_VIEW_STATE_ENTERED; + run_fn(v->hooks.on_enter, v); +} +/* 离开一个 view(清 ENTERED + on_leave;默认不销毁子 view/widget——弹回零重建)。 */ +static void view_leave(meow_view *v) +{ + if (!v || !(v->state & MEOW_VIEW_STATE_ENTERED)) return; + v->state &= ~MEOW_VIEW_STATE_ENTERED; + run_fn(v->hooks.on_leave, v); +} + +/* ============================================================ */ +/* 生命周期 */ +/* ============================================================ */ +meow_view *meow_view_create(int id, meow_view_type type, + const meow_view_hooks *hooks, void *ctx) +{ + meow_view *v = (meow_view *)calloc(1, sizeof *v); + if (!v) return NULL; + v->id = id; + v->type = (type == MEOW_VIEW_CONTAINER) ? MEOW_VIEW_CONTAINER : MEOW_VIEW_ACTIVITY; + v->x = v->y = v->w = v->h = 0; + v->z = 0; + v->state = MEOW_VIEW_STATE_CREATED; + if (hooks) v->hooks = *hooks; + v->ctx = ctx; + view_map_set(v); + meow_draw_view_register(v); /* 登记为绘制物(on_draw 经 z 序重画触发) */ + run_fn(v->hooks.on_create, v); + if (v->hooks.on_layout) run_fn(v->hooks.on_layout, v); /* 布局:create 后跑一次 */ + return v; +} + +void meow_view_free(meow_view *v) +{ + if (!v) return; + if (v->state & MEOW_VIEW_STATE_ENTERED) view_leave(v); + run_fn(v->hooks.on_destroy, v); /* 释放视图态资源 */ + view_map_clear(v); + meow_draw_view_unregister(v); /* 注销 z 序绘制物 */ + for (int i = 0; i < v->nwidgets; i++) /* 叶子控件 */ + meow_widget_free(v->widgets[i]); + v->nwidgets = 0; + for (int i = 0; i < v->nchildren; i++) /* 子 view 递归 */ + meow_view_free(v->children[i]); + v->nchildren = 0; + free(v); +} + +/* ============================================================ */ +/* 嵌套(组合视图树) */ +/* ============================================================ */ +int meow_view_add_child(meow_view *parent, meow_view *child) +{ + if (!parent || !child || child == parent) return -1; + for (int i = 0; i < parent->nchildren; i++) + if (parent->children[i] == child) return -1; /* 去重 */ + if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1; + parent->children[parent->nchildren++] = child; + child->parent = parent; + return 0; +} + +int meow_view_add_widget(meow_view *v, meow_widget *w) +{ + if (!v || !w) return -1; + for (int i = 0; i < v->nwidgets; i++) + if (v->widgets[i] == w) return -1; + if (v->nwidgets >= MEOW_VIEW_MAX_WIDGET) return -1; + v->widgets[v->nwidgets++] = w; + meow_draw_widget_set_view(w, v); /* z 深度源 = view 树;重算 auto z */ + return 0; +} + +void meow_view_set_z(meow_view *v, int z) +{ + if (!v) return; + v->z = (z < 0) ? 0 : z; + meow_draw_view_set_z(v, v->z); +} +int meow_view_z(const meow_view *v) +{ + return v ? v->z : 0; +} + +/* ============================================================ */ +/* 页面导航(包装 activity 栈——唯一驱动 on_enter/on_leave) */ +/* ============================================================ */ +void meow_view_page_push(int id) +{ + meow_view *cur = g_top_view; + meow_view *next = view_map_get(id); + if (cur && cur != next) view_leave(cur); + meow_ui_activity_push(id); + view_enter(next); + g_top_view = next ? next : cur; +} + +void meow_view_page_pop(void) +{ + meow_view *cur = g_top_view; + view_leave(cur); + meow_ui_activity_pop(); + meow_view *next = view_map_get(meow_ui_activity_active()); + view_enter(next); + g_top_view = next ? next : NULL; +} + +/* 说明(诚实):replace 假定目标页已由 App 先 meow_view_create 指定 id 建好;本批不销毁旧页 + * 对象(保留弹回),仅 leave+enter 切换——与设计「leave+destroy 当前」的差异见汇报。 */ +void meow_view_page_replace(int id) +{ + meow_view *cur = g_top_view; + meow_view *next = view_map_get(id); + if (cur && cur != next) view_leave(cur); + view_enter(next); + g_top_view = next ? next : cur; +} + +/* ---- 页内子导航(预留扩展位 a 桩:本批不实现,仅返回占位)---- */ +int meow_view_sub_push(meow_view *v, meow_view *sub) +{ + (void)v; (void)sub; + return 0; /* 预留:将来在 ACTIVITY view 内维护子栈 */ +} +meow_view *meow_view_sub_pop(meow_view *v) +{ + (void)v; + return NULL; /* 预留 */ +} + +/* ============================================================ */ +/* 坐标 / 查询 */ +/* ============================================================ */ +int meow_view_screen_x(const meow_view *v) +{ + int x = v ? v->x : 0; + const meow_view *p = v ? v->parent : NULL; + while (p) { x += p->x; p = p->parent; } + return x; +} +int meow_view_screen_y(const meow_view *v) +{ + int y = v ? v->y : 0; + const meow_view *p = v ? v->parent : NULL; + while (p) { y += p->y; p = p->parent; } + return y; +} + +meow_view *meow_view_parent(const meow_view *v) { return v ? v->parent : NULL; } +int meow_view_get_state(const meow_view *v) { return v ? (int)v->state : 0; } diff --git a/meow_ui_view.h b/meow_ui_view.h new file mode 100644 index 0000000..0b221b9 --- /dev/null +++ b/meow_ui_view.h @@ -0,0 +1,98 @@ +/* + * meow_ui_view.h — SDK 视图/页面框架(meow_view,嵌套容器 + 生命周期 + 事件路由)。 + * + * 源:devdocs/plan/SDK_VIEW_FRAMEWORK_DESIGN.md §A。本文件为机制 ABI(结构 + 生命周期钩子 + + * 页面导航包装),与 z 序绘制物注册表(meow_ui_draw.c,§C/§6.2)协同: + * - meow_view 是**薄壳**:容器 + 生命周期 + 事件路由;叶子仍是 meow_widget(不改)。 + * - on_draw 是回调,view 不缓存像素(非保留);重绘由 meow_ui_invalidate 触发。 + * - opt-in:不调用 meow_view_* 的 App 完全照旧(widget/activity/flush 不动)。 + * - z 深度源 = view 树(parent/children 深度);叶子 widget 深度 = 所属 view 深度 + 1。 + * + * 页面模型(定案 b,预留 a):顶层页切换仍由 activity 栈(meow_view_page_push/pop/replace 包装), + * 不做页内子导航栈;meow_view_sub_push/pop 为将来扩展位(仅在一个 ACTIVITY view 内维护子栈)。 + */ +#ifndef MEOW_UI_VIEW_H +#define MEOW_UI_VIEW_H + +#include "meow_ui.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct meow_view meow_view; + +typedef enum { MEOW_VIEW_ACTIVITY = 0, MEOW_VIEW_CONTAINER = 1 } meow_view_type; +typedef enum { + MEOW_VIEW_STATE_CREATED = 1u << 0, + MEOW_VIEW_STATE_ENTERED = 1u << 1, + MEOW_VIEW_STATE_DIRTY = 1u << 2, +} meow_view_state; + +/* 生命周期/事件钩子(on_create→on_enter→(事件循环)→on_leave→on_destroy + on_layout)。 + * 均以 view 自身 + ctx(=视图控制器,业务-UI 绑定点)为参。可 NULL。 */ +typedef void (*meow_view_fn)(meow_view *v, void *ctx); +typedef int (*meow_view_ev_fn)(meow_view *v, int ev, int a, int b, void *ctx); +typedef void (*meow_view_draw_fn)(meow_view *v, const meow_theme *th, void *ctx); + +typedef struct meow_view_hooks { + meow_view_fn on_create; /* 建 view 后跑一次(分配视图态/初始布局) */ + meow_view_fn on_enter; /* 页面进入(push/replace 后;回页面自动恢复零重建) */ + meow_view_fn on_leave; /* 页面离开(pop/replace 前;默认不销毁子 view/widget) */ + meow_view_fn on_destroy; /* meow_view_free 释放(递归)时跑 */ + meow_view_fn on_layout; /* 布局(on_create/page_push 后跑一次,算子控件绝对坐标) */ + meow_view_draw_fn on_draw; /* 绘制回调(非保留;由 meow_ui_invalidate 触发) */ + meow_view_ev_fn on_event; /* 事件路由(可 NULL;先发后代按 z 降序,命中即消费) */ +} meow_view_hooks; + +#define MEOW_VIEW_MAX_CHILD 16 +#define MEOW_VIEW_MAX_WIDGET 32 + +struct meow_view { + int type; /* MEOW_VIEW_ACTIVITY / MEOW_VIEW_CONTAINER */ + int id; /* 页面/视图 id(App 命名的稳定值;= page_push 用的 id) */ + int x, y, w, h; /* 自身坐标(相对父内容原点;子 view 默认占满,叠放由 z 排) */ + meow_view *parent; + meow_view *children[MEOW_VIEW_MAX_CHILD]; + int nchildren; + meow_widget *widgets[MEOW_VIEW_MAX_WIDGET]; /* 叶子控件(直接挂本 view) */ + int nwidgets; + int z; /* 显式 z(meow_view_set_z);0=未设(由子/注册表推导) */ + unsigned state; /* meow_view_state 位掩码 */ + meow_view_hooks hooks; + void *ctx; /* 视图控制器(biz-UI 绑定点) */ +}; + +/* ---- 生命周期 ---- */ +meow_view *meow_view_create(int id, meow_view_type type, + const meow_view_hooks *hooks, void *ctx); +void meow_view_free(meow_view *v); /* 递归 on_destroy + widget_free 叶子 + 注销 z 序 */ + +/* ---- 嵌套(组合视图树)---- */ +int meow_view_add_child(meow_view *parent, meow_view *child); /* 任意深挂子 view;0/负=结果 */ +int meow_view_add_widget(meow_view *v, meow_widget *w); /* 挂叶子控件(登记 z 深度源) */ +void meow_view_set_z(meow_view *v, int z); /* 显式 z(999 置顶);影响其绘制物 z */ +int meow_view_z(const meow_view *v); + +/* ---- 页面导航(包装 activity 栈,唯一驱动 on_enter/on_leave——单一入口,App 不再分别调)---- + * 顶层页切换走 activity 栈;页内子导航为预留(见设计定案 b)。 */ +void meow_view_page_push(int id); /* activity_push + 旧top on_leave + 新页 on_enter */ +void meow_view_page_pop(void); /* 旧top on_leave + activity_pop + 新top on_enter */ +void meow_view_page_replace(int id); /* 旧top on_leave + 新页 on_enter(目标页须已 create) */ + +/* ---- 页内子导航(预留扩展位 a;本批为空实现/桩,仅在 ACTIVITY view 内维护子栈)---- */ +int meow_view_sub_push(meow_view *v, meow_view *sub); /* 预留;返回 0(未实现于本批) */ +meow_view *meow_view_sub_pop(meow_view *v); /* 预留;返回 NULL(未实现于本批) */ + +/* ---- 坐标:view 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */ +int meow_view_screen_x(const meow_view *v); +int meow_view_screen_y(const meow_view *v); + +/* ---- 查询 ---- */ +meow_view *meow_view_parent(const meow_view *v); +int meow_view_get_state(const meow_view *v); /* MEOW_VIEW_STATE_* 位掩码 */ + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_VIEW_H */ diff --git a/meow_ui_widget.c b/meow_ui_widget.c index c6a473f..0881ae8 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -211,10 +211,16 @@ static meow_widget *live_btn_hit(int x, int y) return r; } -/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */ +/* 重绘并局部刷新一个按钮区。 + * §6.2 收敛:若按钮已注册绘制物 → 走 meow_ui_invalidate(清底 + 按 z 重画重叠区完整 z 栈), + * 避免按钮压上层 chrome 时被单控件 flush 抹掉(P1);未注册则回退单控件绘制+区域上屏。 */ static void live_btn_redraw(meow_widget *w) { if (!w) return; + if (meow_draw_widget_registered(w)) { + meow_ui_invalidate(w->x, w->y, w->w, w->h); + return; + } meow_widget_draw(w); meow_ui_flush_region(w->x, w->y, w->w, w->h); } @@ -275,6 +281,7 @@ meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label) t->state = MEOW_WS_ENABLED; /* §5.8.3:默认 ENABLED */ if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } live_btn_register(t); + meow_draw_widget_register(t); /* §6.2 自动注册(创建即注册,App 零调用) */ return t; } @@ -286,6 +293,7 @@ meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; + meow_draw_widget_register(t); return t; } @@ -298,6 +306,7 @@ meow_widget *meow_switch_create(int x, int y, int on) t->state = MEOW_WS_ENABLED; t->on = on ? 1 : 0; if (t->on) t->state |= MEOW_WS_SELECTED; /* §5.8.3:开关 on → selected */ + meow_draw_widget_register(t); return t; } @@ -310,12 +319,14 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count, t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; + meow_draw_widget_register(t); return t; } void meow_widget_free(meow_widget *w) { if (!w) return; + meow_draw_widget_unregister(w); /* §6.2:绘制物注册表自动注销(stale id 失效勿用) */ live_btn_unregister(w); free(w); } @@ -334,6 +345,7 @@ meow_widget *meow_widget_custom_create(int x, int y, int w, int h, t->render_cb = render_cb; t->hit_cb = hit_cb; t->ctx = ctx; + meow_draw_widget_register(t); return t; } @@ -346,6 +358,7 @@ meow_widget *meow_row_create(int x, int y, int w, int h, int spacing) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = spacing > 0 ? spacing : 0; + meow_draw_widget_register(t); return t; } meow_widget *meow_column_create(int x, int y, int w, int h, int spacing) @@ -356,6 +369,7 @@ meow_widget *meow_column_create(int x, int y, int w, int h, int spacing) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = spacing > 0 ? spacing : 0; + meow_draw_widget_register(t); return t; } meow_widget *meow_box_create(int x, int y, int w, int h) @@ -366,6 +380,7 @@ meow_widget *meow_box_create(int x, int y, int w, int h) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = 0; + meow_draw_widget_register(t); return t; } meow_widget *meow_layout_spacer(int w, int h) @@ -375,6 +390,7 @@ meow_widget *meow_layout_spacer(int w, int h) t->type = W_SPACER; t->x = 0; t->y = 0; t->w = w > 0 ? w : 0; t->h = h > 0 ? h : 0; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; + meow_draw_widget_register(t); return t; } @@ -421,6 +437,24 @@ void meow_layout_arrange(meow_widget *layout) } } +/* ---- 布局深度反推访问器(meow_ui_draw.c 用,读本 TU 的 struct meow_widget 内部字段)---- */ +int meow_widget_is_layout(const meow_widget *w) +{ return w && w->type == W_LAYOUT; } +int meow_widget_nchild(const meow_widget *w) +{ return (w && w->type == W_LAYOUT) ? w->nchildren : 0; } +const meow_widget *meow_widget_child_at(const meow_widget *w, int idx) +{ return (w && w->type == W_LAYOUT && idx >= 0 && idx < w->nchildren) ? w->children[idx] : NULL; } + +/* ---- 几何/归属访问器(meow_ui_draw.c 用:绘制物 rect 实时读 + owner 过滤)---- */ +int meow_widget_owner(const meow_widget *w) +{ return w ? w->owner : 0; } +void meow_widget_rect(const meow_widget *w, int out[4]) +{ + if (!out) return; + if (!w) { out[0] = out[1] = out[2] = out[3] = 0; return; } + out[0] = w->x; out[1] = w->y; out[2] = w->w; out[3] = w->h; +} + /* ============================================================ */ /* §5.8.6 batch2 Material 组件子集 API(create/setter/getter/hit) */ /* ============================================================ */ @@ -460,6 +494,7 @@ meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *place t->buf = buf; t->buflen = buf ? (int)strlen(buf) : 0; t->maxlen = (maxlen > 0 && maxlen <= 256) ? maxlen : 256; t->ime_mode = mode; + meow_draw_widget_register(t); return t; } void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder) @@ -515,6 +550,7 @@ meow_widget *meow_card_create(int x, int y, int w, int h) t->type = W_CARD; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; + meow_draw_widget_register(t); return t; } void meow_card_set_content(meow_widget *card, meow_widget *child) @@ -535,6 +571,7 @@ meow_widget *meow_divider_create(int x, int y, int len, int vertical) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->variant = vertical ? 1 : 0; + meow_draw_widget_register(t); return t; } @@ -547,6 +584,7 @@ meow_widget *meow_badge_create(int cx, int cy) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->value = 0; /* 红点 */ + meow_draw_widget_register(t); return t; } void meow_badge_set(meow_widget *w, int count) @@ -564,6 +602,7 @@ meow_widget *meow_checkbox_create(int x, int y) t->type = W_CHECKBOX; t->x = x; t->y = y; t->w = 34; t->h = 34; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; + meow_draw_widget_register(t); return t; } meow_widget *meow_radio_create(int x, int y) @@ -573,6 +612,7 @@ meow_widget *meow_radio_create(int x, int y) t->type = W_RADIO; t->x = x; t->y = y; t->w = 34; t->h = 34; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; + meow_draw_widget_register(t); return t; } @@ -586,6 +626,7 @@ meow_widget *meow_progress_create(int x, int y, int w, int h, int value) t->state = MEOW_WS_ENABLED; t->variant = 0; /* 线性 */ t->value = value < 0 ? -1 : (value > 100 ? 100 : value); + meow_draw_widget_register(t); return t; } meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value) @@ -603,6 +644,7 @@ meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value) t->state = MEOW_WS_ENABLED; t->variant = 1; /* 圆形 */ t->value = value < 0 ? -1 : (value > 100 ? 100 : value); + meow_draw_widget_register(t); return t; } void meow_progress_set(meow_widget *w, int value) @@ -621,6 +663,7 @@ meow_widget *meow_fab_create(int x, int y, int size, const char *label) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + meow_draw_widget_register(t); return t; } @@ -634,6 +677,7 @@ meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title t->state = MEOW_WS_ENABLED; t->back_on = 0; t->nitems = 0; if (title) { strncpy(t->label, title, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + meow_draw_widget_register(t); return t; } void meow_topappbar_set_title(meow_widget *w, const char *title) @@ -682,6 +726,7 @@ meow_widget *meow_navigationbar_create(int x, int y, int w, int h) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->nitems = 0; t->selected = 0; + meow_draw_widget_register(t); return t; } int meow_navigationbar_add_item(meow_widget *w, const char *label) @@ -721,6 +766,7 @@ meow_widget *meow_tabbar_create(int x, int y, int w, int h) t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->nitems = 0; t->selected = 0; + meow_draw_widget_register(t); return t; } int meow_tabbar_add_tab(meow_widget *w, const char *label) diff --git a/test_ime_fix.c b/test_ime_fix.c index ba170aa..74f5891 100644 --- a/test_ime_fix.c +++ b/test_ime_fix.c @@ -8,6 +8,7 @@ * gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_draw.c meow_ui_view.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread */ #include "meow_ui.h" diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c index 4b1b447..18d4c59 100644 --- a/test_meow_ui_widgets.c +++ b/test_meow_ui_widgets.c @@ -5,6 +5,7 @@ * gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_draw.c meow_ui_view.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread * 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕) */ diff --git a/test_p1p2p3_repro.c b/test_p1p2p3_repro.c index eaefad4..f8e89f2 100644 --- a/test_p1p2p3_repro.c +++ b/test_p1p2p3_repro.c @@ -18,12 +18,13 @@ * -o test_p1p2p3_repro test_p1p2p3_repro.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c stb_image.c \ + * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \ + * meow_ui_view.c stb_image.c \ * -I. -I../hello_app -I../meow_launcherd -pthread -lm * 运行:MEOW_UI_DUMP=/tmp/repro.ppm ./test_p1p2p3_repro (optional,dump 最终屏幕) * - * 修复前(无 P1/P2/P3 修法)预期输出:RESULT: 4 FAIL(S); - * 修复后:RESULT: ALL PASS。 + * 覆盖(批次① z 序下沉后):P1c 显式 z 置顶(set_z(g_fab,999)+invalidate(r_prc) 不抹 FAB—— + * 显式 z 不依赖绘制先后)+ P2/P3 与迁移前一致。预期 RESULT: ALL PASS。 */ #include "meow_ui.h" #include "meow_ui_internal.h" @@ -74,13 +75,15 @@ int main(void) int fails = 0; - /* ================= P1:局部重绘不盖浮层(FAB) ================= - * 场景:scroll=0 时圆形进度(413..453,605..645) 与 FAB(406..462,616..672) 重叠。 - * demo_paint 先画内容后画 chrome → 场景正确 FAB 在上。随后「局部重绘进度条」: + /* ================= P1 / P1c:局部重绘不盖浮层(FAB,须显式置顶) ================= + * 场景:scroll=0 时圆形进度 r_prc(413..453,605..645) 与 FAB(406..462,616..672) 重叠。 + * demo_paint 整帧后 FAB 在上(demo_init set_z(g_fab,999) 显式置顶)。随后「局部重绘进度条」: * 旧(清底+只画进度条+flush 其矩形)→ FAB 重叠区被清掉; - * 新(清底+重画重叠区内所有控件=hello d_redraw/d_paint_region)→ FAB 保持。 */ + * 新(SDK meow_ui_invalidate:清底 + 按 z 重画重叠区完整 z 栈)→ FAB 保持。 + * P1c:set_z(g_fab,999) 后 invalidate(r_prc),断言 FAB 重叠点(434,622) 仍=demo_paint 后值 + * (显式 z 不依赖绘制先后——置顶在重绘前显式给出,下层重绘不抹上层)。 */ demo_paint(); - int fx = r_fab.x + 28, fy = r_fab.y + 6; /* FAB 内、远离 "+" 文本处 */ + int fx = r_fab.x + 28, fy = r_fab.y + 6; /* FAB 内、远离 "+" 文本处 → (434,622) */ int s0r, s0g, s0b; rd_px(fx, fy, &s0r, &s0g, &s0b); printf("P1 scene (demo_paint) FAB-overlap "); pr_px("scene", fx, fy); if (is_bg(s0r, s0g, s0b)) @@ -98,15 +101,16 @@ int main(void) if (is_bg(obr, obg, obb)) printf(" [BUG-CONFIRMED] old path erased FAB overlap (bg)\n"); else printf(" [note] old path did NOT erase FAB here (color %02X%02X%02X)\n", obr, obg, obb); - /* 新式局部重绘(真实 hello d_redraw → d_paint_region,重画重叠栈)*/ + /* P1c:显式置顶 + SDK 局部重画(invalidate 按 z 重画重叠栈) */ demo_paint(); /* 还原场景 */ + meow_widget_set_z(g_fab, 999); /* 重新显式置顶(P1c 要求) */ meow_progress_set(g_dprc, 30); - d_redraw(g_dprc, r_prc); /* hello 的局部重绘入口 */ + meow_ui_invalidate(r_prc.x, r_prc.y, r_prc.w, r_prc.h); int nbr, nbg, nbb; rd_px(fx, fy, &nbr, &nbg, &nbb); - printf("P1 new-partial (d_redraw stack) "); pr_px("new", fx, fy); + printf("P1c set_z(999)+invalidate(r_prc) "); pr_px("p1c", fx, fy); if (near(s0r, nbr) && near(s0g, nbg) && near(s0b, nbb)) - printf(" P1-PASS FAB preserved over live progress redraw\n"); - else { printf(" P1-FAIL FAB not preserved (scene %02X%02X%02X vs new %02X%02X%02X)\n", + printf(" P1-PASS explicit z:FAB preserved over live progress redraw\n"); + else { printf(" P1-FAIL FAB not preserved (scene %02X%02X%02X vs p1c %02X%02X%02X)\n", s0r, s0g, s0b, nbr, nbg, nbb); fails++; } /* ================= P2:列表焦点按本次触摸绑定/释放 ================= @@ -128,7 +132,7 @@ int main(void) printf("P3 list rect = (%d,%d,%d,%d)\n", r_list.x, r_list.y, r_list.w, r_list.h); /* 顶部泄漏:选中行0 + scroll=20 → 行0 ry=top-20,填充越顶泄漏 */ meow_list_set_selected(g_dlc, 0); meow_list_set_scroll(g_dlc, 20); - d_redraw(g_dlc, r_list); + meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); int ux = r_list.x + 10, uy = r_list.y - 5; /* 列表顶上方 5px(标题间隙) */ int ur, ug, ub; rd_px(ux, uy, &ur, &ug, &ub); printf("P3 top-leak "); pr_px("above-top", ux, uy); @@ -136,7 +140,7 @@ int main(void) else { printf(" P3-top-FAIL leaked into title gap\n"); fails++; } /* 底部泄漏:选中行4 + scroll=20 → 行4 ry=590..630 越底泄漏 */ meow_list_set_selected(g_dlc, 4); meow_list_set_scroll(g_dlc, 20); - d_redraw(g_dlc, r_list); + meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); int dx = r_list.x + 10, dy_ = r_list.y + r_list.h + 5; /* 列表底下方 5px */ int dr, dg, db; rd_px(dx, dy_, &dr, &dg, &db); printf("P3 bot-leak "); pr_px("below-bottom", dx, dy_); diff --git a/test_sdk_fixes.c b/test_sdk_fixes.c index e2a3401..311396c 100644 --- a/test_sdk_fixes.c +++ b/test_sdk_fixes.c @@ -10,6 +10,7 @@ * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_draw.c meow_ui_view.c \ * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread */ #include "meow_ui.h" From b6b31f19678147360cfe53864a7e0aea8c3059ae Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 03:11:35 +0800 Subject: [PATCH 23/49] =?UTF-8?q?fix(perf+sdk/ime):=20=E2=91=A0perf=20net?= =?UTF-8?q?=5Fjson=20=E7=BC=BA=20network=20=E5=AF=B9=E8=B1=A1=E9=97=AD?= =?UTF-8?q?=E5=90=88=E6=8B=AC=E5=8F=B7=20=E2=86=92=20=E5=85=A8=E9=87=8F=20?= =?UTF-8?q?JSON=20=E6=A0=B9=E5=AF=B9=E8=B1=A1=E4=B8=8D=E9=97=AD=E5=90=88/?= =?UTF-8?q?=E9=9D=9E=E6=B3=95=EF=BC=88MeowPanel=20/api/system=20jv=5Fparse?= =?UTF-8?q?=20=E5=A4=B1=E8=B4=A5=E6=A0=B9=E5=9B=A0=EF=BC=8C=E7=9C=9F?= =?UTF-8?q?=E6=9C=BA+python=20=E5=8F=8C=E9=87=8D=E8=AF=81=E5=AE=9E?= =?UTF-8?q?=EF=BC=89=E2=91=A1IME=20flush=20=E6=8C=82=E8=B5=B7(meow=5Fgfx?= =?UTF-8?q?=5Fflush=5Fhold):=20=E9=98=BB=E5=A1=9E=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E6=95=B4=E5=91=A8=E6=9C=9F=E5=8F=AA=E8=AE=B0=E8=84=8F=E3=80=81?= =?UTF-8?q?IME=E7=94=BB=E5=AE=8C=E4=B8=80=E6=AC=A1=E5=8E=9F=E5=AD=90?= =?UTF-8?q?=E4=B8=8A=E5=B1=8F=EF=BC=8C=E6=A0=B9=E6=B2=BB=20on=5Ftick=20?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E6=9D=A1=E4=B8=8E=E9=94=AE=E7=9B=98=E5=90=8C?= =?UTF-8?q?=E5=8C=BA=E4=B8=A4=E5=88=B7=E9=A2=91=E9=97=AA=20=E2=91=A2TextFi?= =?UTF-8?q?eld=20on=5Fchange=20=E5=8F=AA=E6=A0=87=E8=84=8F=E4=B8=8D?= =?UTF-8?q?=E9=87=8D=E7=94=BB=E2=86=92=E6=94=B9=20meow=5Fui=5Finvalidate?= =?UTF-8?q?=20=E7=9C=9F=E9=87=8D=E7=94=BB=EF=BC=8C=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E5=9B=9E=E6=98=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_svc_perf fa073bf9→7ea196de; meow_ui.a d814d0ed→ece0ecdb; hello 2ce09466→f45ba051; launcher 9ca4c5e8→bd37c8b0(重链) 宿主验证: test_ime_fix/test_p1p2p3_repro/test_meow_ui_widgets 全 PASS; perf_host all 输出经 python json 校验合法 --- meow_ui_gfx.c | 10 ++++++++-- meow_ui_ime.c | 5 +++++ meow_ui_internal.h | 4 ++++ meow_ui_widget.c | 6 ++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index fdcd3fc..e2dd313 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -179,6 +179,12 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } void meow_gfx_mark_full(void) { dirty_full(); } void meow_gfx_dirty_reset(void) { dirty_reset(); } +/* flush 挂起(IME/模态阻塞循环用):挂起期间 flush/flush_region 只记脏不上屏, + * 解除后由调用方一次 flush 原子上屏——避免「App on_tick 重绘先上屏、覆盖层随后再上屏」 + * 同一区域一帧两刷的频闪。 */ +static int g_flush_hold = 0; +void meow_gfx_flush_hold(int on) { g_flush_hold = on ? 1 : 0; } + /* back_buf 每行字节数 / 位深(M2.71-bug3:模态/IME 整帧快照恢复用)。 */ int meow_ui_back_buf_pitch(void) { return g_line; } int meow_ui_back_buf_bpp(void) { return g_bpp; } @@ -918,10 +924,10 @@ static void present(void) dirty_reset(); } -void meow_ui_flush(void) { present(); } +void meow_ui_flush(void) { if (!g_flush_hold) present(); } void meow_ui_flush_region(int x, int y, int w, int h) -{ dirty_add(x, y, w, h); present(); } +{ dirty_add(x, y, w, h); if (!g_flush_hold) present(); } /* ---- 帧率调度设置接口(launcher 与 app 共用;归口 PERF_SCHEDULER 显示刷新率子域)---- */ /* 设目标档位(60/30/15):同时把当前有效档对齐到目标(与 launcher settings_load 语义一致)。 */ diff --git a/meow_ui_ime.c b/meow_ui_ime.c index ce15065..968566d 100644 --- a/meow_ui_ime.c +++ b/meow_ui_ime.c @@ -268,6 +268,10 @@ int meow_ui_text_input(meow_ime_ctx *ctx) meow_ui_text_input_async(ctx); /* 阻塞循环:直到 done;on_change 由 handle_tap 触发 */ while (meow_ime_active()) { + /* flush 挂起:整个周期内(事件处理触发的 on_change 重绘 + App on_tick 重绘) + * 只记脏不上屏;IME 画完后一次 flush 原子上屏——否则 on_tick 的 invalidate + * 会先把进度条等区域刷上屏盖住键盘,IME 随后再盖回,同区一帧两刷→频闪。 */ + meow_gfx_flush_hold(1); meow_input_ev e; while (meow_input_pop(&e)) { meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */ @@ -277,6 +281,7 @@ int meow_ui_text_input(meow_ime_ctx *ctx) * 顺序要求:先喂 on_tick(App 重绘进 back_buf)→ 再 meow_ime_draw(IME 面板随后盖上去), * 保证 IME 面板覆盖在最上,不被 App 的 d_paint_region 覆盖。 */ meow_ui_feed_tick(); + meow_gfx_flush_hold(0); meow_ime_draw(); meow_ui_flush(); usleep(16000); diff --git a/meow_ui_internal.h b/meow_ui_internal.h index bc3c89b..9e7fc22 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -98,6 +98,10 @@ void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alp /* 标记一个区域脏(供非标准绘制使用)。 */ void meow_gfx_mark_dirty(int x, int y, int w, int h); +/* flush 挂起(IME/模态阻塞循环用):on=1 期间 meow_ui_flush/flush_region 只记脏不上屏, + * 解除后由调用方一次 flush 原子上屏,避免覆盖层与 App on_tick 重绘同帧两刷频闪。 */ +void meow_gfx_flush_hold(int on); + /* 标记整帧脏(launcher 手动脏模型桥接:自绘 raw 像素后标全屏,present 走 SDK)。 */ void meow_gfx_mark_full(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index 0881ae8..21b428b 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -507,7 +507,8 @@ void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder) meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); } const char *meow_textfield_text(meow_widget *w) { return (w && w->type == W_TEXTFIELD && w->buf) ? w->buf : ""; } -/* 输入中实时把 IME 文本同步到字段缓冲 + 只刷字段区(字段在顶部,IME 键盘在底部覆盖层) */ +/* 输入中实时把 IME 文本同步到字段缓冲 + 重画字段区(invalidate 重画 z 栈相交绘制物; + * flush 在 IME 阻塞循环内被挂起(meow_gfx_flush_hold),随本周期末一次原子上屏) */ static void textfield_change_cb(const char *text, void *ctx) { meow_widget *w = (meow_widget *)ctx; @@ -517,7 +518,8 @@ static void textfield_change_cb(const char *text, void *ctx) w->buf[w->maxlen - 1] = 0; } w->buflen = text ? (int)strlen(text) : 0; - meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + /* 只标脏不重画的话 back_buf 仍是旧文本,flush 上去的还是旧的——必须真重画 */ + meow_ui_invalidate(w->x, w->y, w->w, w->h); } int meow_textfield_activate(meow_widget *w) { From 29eb8e4737161a149e687106f42d61032e15ea2d Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 03:31:09 +0800 Subject: [PATCH 24/49] =?UTF-8?q?refactor(hello):=20=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=89=B9=E6=AC=A1=E2=91=A1=20=E2=80=94=20hel?= =?UTF-8?q?lo.c=20=E6=8B=86=E5=88=86=E4=B8=BA=20app.c+ui/{home,full,demo}.?= =?UTF-8?q?c+ui/common/chrome.c+logic/{notify,state}.c=EF=BC=88=E5=AE=9A?= =?UTF-8?q?=E6=A1=88B:=20demo=E8=A7=86=E5=9B=BE=E6=80=81=E7=95=99ui/?= =?UTF-8?q?=EF=BC=9B=E5=AE=9A=E6=A1=88b:=20page=E5=AF=BC=E8=88=AA=E5=8C=85?= =?UTF-8?q?=E8=A3=85activity=E6=A0=88=EF=BC=89=EF=BC=9Btest=5Fp1p2p3=5Frep?= =?UTF-8?q?ro=20=E6=94=B9=E5=A4=9A=E6=96=87=E4=BB=B6=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=BD=A2=E6=80=81=20(agent-177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello f45ba051→c06b3b4f; mipsel -Wall -Wextra 0 warning; 宿主 P1c/P2/P3+widgets 全 PASS 出入(诚实): view on_draw/on_event 路径暂未启用(注册表直绘已覆盖, on_draw 双画风险), 留批③实测; home/full 仍手绘未迁注册表 --- test_p1p2p3_repro.c | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test_p1p2p3_repro.c b/test_p1p2p3_repro.c index f8e89f2..0811786 100644 --- a/test_p1p2p3_repro.c +++ b/test_p1p2p3_repro.c @@ -1,41 +1,43 @@ /* test_p1p2p3_repro.c — 宿主复现并校验 P1/P2/P3 三个真机问题的修复(离屏 480x720,16bpp)。 * - * 方法:#include 真实 `hello.c`(改 hello 的 main 名),直接驱动 demo 的真实函数 - * (demo_paint / d_redraw / demo_touch_down / demo_rebuild_to),对 back_buf 做像素断言。 + * 方法:直接驱动拆分后 hello 的 demo 真实函数(demo_paint / demo_rebuild_to / demo_touch_down) + * 与 demo 视图态(extern,见 ../hello_app/hello_internal.h),对 back_buf 做像素断言。 * 这样测的是「真实 demo 代码 + 真实 SDK」,不是模拟桩。不依赖 /dev/fb0 / 字库 / launcher。 * + * 批次② 变化:hello.c 已拆为 app.c + ui/{home,full,demo}.c + ui/common/chrome.c + + * logic/{notify,state}.c;本文件不再 #include hello.c,改为 extern 驱动 demo 层并链接多文件。 + * * 覆盖: * P1 z-order 叠层:圆形进度条(实时重绘)与 FAB 浮层重叠时,局部重绘不再清掉/盖住 FAB。 - * 旧 d_redraw=清底+只画单控件 → FAB 重叠区被抹成背景;新 d_redraw=d_paint_region - * 重画重叠区完整 z 栈(内容→标题→slider值→chrome)→ FAB 保持。 + * (批次① 后为 P1c:set_z(g_fab,999)+invalidate(r_prc),显式 z 不依赖绘制先后。) * P2 list 焦点:命中非 list 区域时释放列表焦点、切页滚动(不再沿用旧焦点继续滚列表)。 - * P3 list 脏区:W_LIST 绘制已裁剪到视口;滚动(非整行偏移)+选中行不再向标题间隙/底缘下泄漏 - * 填充(此前泄漏会标脏并上屏 → 标题栏/底部残留)。 + * P3 list 脏区:W_LIST 绘制已裁剪到视口;滚动(非整行偏移)+选中行不再向标题间隙/底缘下泄漏。 * * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime/img/config/ - * notify/ipc + 打桩 font/global(test_host_stub) + stb_image): - * gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -Dmain=hello_app_main \ - * -o test_p1p2p3_repro test_p1p2p3_repro.c test_host_stub.c \ - * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ - * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \ - * meow_ui_view.c stb_image.c \ - * -I. -I../hello_app -I../meow_launcherd -pthread -lm + * notify/ipc + 打桩 font/global(test_host_stub) + stb_image + hello 拆分层): + * 1) 单独编译 app.c 并把 main 改名 hello_app_main(否则与本文件 main 冲突): + * gcc -c -Wall -Wextra -DMEOW_UI_HOST_TEST -Dmain=hello_app_main \ + * -I. -I../hello_app ../hello_app/app.c -o app.o + * 2) 编译本文件 + 其余 hello 模块 + SDK 源 + 打桩,链接 app.o: + * gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -o test_p1p2p3_repro test_p1p2p3_repro.c app.o \ + * test_host_stub.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \ + * meow_ui_view.c stb_image.c \ + * ../hello_app/ui/home.c ../hello_app/ui/full.c ../hello_app/ui/demo.c \ + * ../hello_app/ui/common/chrome.c ../hello_app/logic/notify.c ../hello_app/logic/state.c \ + * -I. -I../hello_app -I../meow_launcherd -pthread -lm * 运行:MEOW_UI_DUMP=/tmp/repro.ppm ./test_p1p2p3_repro (optional,dump 最终屏幕) * - * 覆盖(批次① z 序下沉后):P1c 显式 z 置顶(set_z(g_fab,999)+invalidate(r_prc) 不抹 FAB—— - * 显式 z 不依赖绘制先后)+ P2/P3 与迁移前一致。预期 RESULT: ALL PASS。 + * 预期 RESULT: ALL PASS(P1c/P2/P3 断言真实有效,未弱化)。 */ #include "meow_ui.h" #include "meow_ui_internal.h" +#include "../hello_app/hello_internal.h" #include #include #include -#define main hello_app_main /* 让 hello.c 的 main 改名,由本文件提供 main */ -#include "hello.c" -#undef main - /* ---- 像素读取 ---- */ static void rd_px(int x, int y, int *r, int *g, int *b) { From d9c5791a478f51e077f19bd5ba6d679d052fee50 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 04:00:15 +0800 Subject: [PATCH 25/49] =?UTF-8?q?refactor(hello):=20=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=89=B9=E6=AC=A1=E2=91=A2=20=E2=80=94=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=B5=8C=E5=A5=97(ui/home/home.c,=20ui/demo/?= =?UTF-8?q?demo.c+ui/demo/list/list.c)=20+=20=E8=A7=86=E5=9B=BE=E6=B7=B1?= =?UTF-8?q?=E5=BA=A6=E5=8C=96(demo=E5=88=97=E8=A1=A8=E5=8C=BA=E6=8A=BD=20C?= =?UTF-8?q?ONTAINER=20=E5=AD=90=E8=A7=86=E5=9B=BE,=20=E5=AD=90>=E7=88=B6?= =?UTF-8?q?=E5=91=BD=E4=B8=AD)=20+=20on=5Fdraw=20=E9=93=BE=E8=B7=AF?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E9=80=9A=E8=BF=87(list=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E6=94=B9=E7=94=B1=E5=AD=90=E8=A7=86=E5=9B=BE=20on=5Fdraw=20?= =?UTF-8?q?=E7=BB=8F=E6=B3=A8=E5=86=8C=E8=A1=A8=E7=BB=98=E5=88=B6)=20+=20g?= =?UTF-8?q?=5Fds.prog=20=E4=B8=8A=E6=94=B6=20logic/state.c=20(agent-181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello c06b3b4f→07b1ee37; mipsel -Wall -Wextra 0 warning; 宿主 P1c/P2/P3 + widgets 全 PASS SDK 后续项(本批 App 层显式驱动适配, 未动 SDK): view enter/leave 不递归 children + on_event 路由未接线 — 待 SDK 侧补 --- test_p1p2p3_repro.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test_p1p2p3_repro.c b/test_p1p2p3_repro.c index 0811786..d779373 100644 --- a/test_p1p2p3_repro.c +++ b/test_p1p2p3_repro.c @@ -6,6 +6,8 @@ * * 批次② 变化:hello.c 已拆为 app.c + ui/{home,full,demo}.c + ui/common/chrome.c + * logic/{notify,state}.c;本文件不再 #include hello.c,改为 extern 驱动 demo 层并链接多文件。 + * 批次③ 变化:ui/home、ui/demo 子目录化(ui/home/home.c + ui/demo/demo.c),demo 列表区块抽成 + * CONTAINER 子视图 ui/demo/list/list.c;下表编译路径相应更新(断言未弱化)。 * * 覆盖: * P1 z-order 叠层:圆形进度条(实时重绘)与 FAB 浮层重叠时,局部重绘不再清掉/盖住 FAB。 @@ -24,8 +26,9 @@ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ * meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \ * meow_ui_view.c stb_image.c \ - * ../hello_app/ui/home.c ../hello_app/ui/full.c ../hello_app/ui/demo.c \ - * ../hello_app/ui/common/chrome.c ../hello_app/logic/notify.c ../hello_app/logic/state.c \ + * ../hello_app/ui/home/home.c ../hello_app/ui/full.c ../hello_app/ui/demo/demo.c \ + * ../hello_app/ui/demo/list/list.c ../hello_app/ui/common/chrome.c \ + * ../hello_app/logic/notify.c ../hello_app/logic/state.c \ * -I. -I../hello_app -I../meow_launcherd -pthread -lm * 运行:MEOW_UI_DUMP=/tmp/repro.ppm ./test_p1p2p3_repro (optional,dump 最终屏幕) * From 44685808e73243eac3d1ebdebf1f5cb46e727712 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 04:39:34 +0800 Subject: [PATCH 26/49] =?UTF-8?q?feat(sdk):=20=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A7=E4=BB=B6=20W=5FLISTVIEW=20(agent-18?= =?UTF-8?q?6)=20=E2=80=94=20MEOW=5FAPP=5FSDK=20=C2=A75.5=20A=E2=91=A1?= =?UTF-8?q?=E8=90=BD=E5=9C=B0:=20=E5=9B=BE=E6=A0=87(=E5=86=85=E7=BD=AEfold?= =?UTF-8?q?er/file=E7=AC=A6=E5=8F=B7=E6=88=96img)+=E6=A0=87=E9=A2=98+?= =?UTF-8?q?=E5=89=AF=E6=A0=87=E9=A2=98+=E5=8F=B3=E4=BE=A7=E6=93=8D?= =?UTF-8?q?=E4=BD=9C(arrow/switch/check/more),=20=E5=8D=95=E9=80=89/?= =?UTF-8?q?=E5=A4=9A=E9=80=89/=E7=A6=81=E6=80=81,=20=E8=A1=8C=E5=8F=B31/3?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=8C=BA=E5=91=BD=E4=B8=AD,=20=E8=A7=86?= =?UTF-8?q?=E5=8F=A3=E8=A3=81=E5=89=AA,=20=E5=85=A8=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E8=89=B2;=20meow=5Fui=5Fimg=5Fscaled=20=E5=BC=B1=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E9=98=B2=E4=BA=8C=E8=BF=9B=E5=88=B6=E8=86=A8=E8=83=80?= =?UTF-8?q?;=20files=20App=20=E8=87=AA=E7=BB=98=E5=88=97=E8=A1=A8=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E8=BF=81=E7=A7=BB(=E9=95=BF=E6=8C=89=E7=95=99App?= =?UTF-8?q?=E5=B1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a ece0ecdb→4335c33a; hello 07b1ee37→07c0c5a5; files 6f6b0a6d→22b6b606 宿主: widgets(含4条listview新断言)/p1p2p3/ime_fix/test_fsx 全 PASS --- meow_ui.h | 72 +++++++++ meow_ui_widget.c | 355 ++++++++++++++++++++++++++++++++++++++++- test_ime_fix.c | 5 +- test_meow_ui_widgets.c | 131 ++++++++++++++- 4 files changed, 558 insertions(+), 5 deletions(-) diff --git a/meow_ui.h b/meow_ui.h index c1df222..b150aef 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -567,6 +567,78 @@ typedef void (*meow_list_tap_fn)(int idx, void *ctx); void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx); /* 命中行→回调 */ int meow_list_tap_at(meow_widget *w, int x, int y); /* 命中行:置选中+回调,返回 idx/-1 */ +/* ============================================================ */ +/* §5.5 设计模板 A② 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作 + 选中 + * ------------------------------------------------------------ + * 在既有「label-only」列表(meow_list_create/lazycolumn,行为不变)之上做**结构化扩展**: + * 行数据源回调逐行给出 meow_list_item(图标 / 标题 / 副标题 / 右侧操作 / 可选中),容器负责 + * 绘制/滚动/选中/命中;支持单选/多选/无选择。向后兼容:既有 meow_list_* 全部保留不动。 + * 颜色全部取 meow_theme_get() 语义色(日/夜/style 自动),不硬编码。 + * 命中:meow_listview_hit 返回行号(含滚动换算 + 禁用行不响应);行右 1/3 为右侧操作区 + * (meow_listview_hit_op 另判)。抬手用最新坐标(goodix 免疫,参照既有 W_LIST 习惯)。 */ +/* ============================================================ */ + +/* 选中模式:无(纯入口列表)/ 单选 / 多选。 */ +typedef enum { MEOW_LIST_SEL_NONE = 0, MEOW_LIST_SEL_SINGLE, MEOW_LIST_SEL_MULTI } meow_list_sel; + +/* 行图标:meow_img*(MEOW_LIST_ICON_IMG)或内置符号(FOLDER/FILE,无图 App 用)。 */ +typedef enum { + MEOW_LIST_ICON_NONE = 0, /* 无图标 */ + MEOW_LIST_ICON_IMG, /* 用 item->icon(meow_img*) */ + MEOW_LIST_ICON_FOLDER, /* 内置目录符号(填充矩形+页签) */ + MEOW_LIST_ICON_FILE, /* 内置文件符号(空心矩形+折角) */ +} meow_list_icon_kind; + +/* 行右侧操作类型:无 / 箭头 / 开关 / 复选 / 详情钮(⋯)。 */ +typedef enum { + MEOW_LIST_RIGHT_NONE = 0, /* 无右侧操作 */ + MEOW_LIST_RIGHT_ARROW, /* 箭头 › */ + MEOW_LIST_RIGHT_SWITCH, /* 开关(on/off = item->on) */ + MEOW_LIST_RIGHT_CHECK, /* 复选标记 */ + MEOW_LIST_RIGHT_MORE, /* ⋯ 详情钮 */ +} meow_list_right; + +/* 单行数据(回调填充到栈上,容器不长期持有)。title 必填;subtitle 可空。 */ +typedef struct meow_list_item { + const meow_img *icon; /* 可选(icon_kind==MEOW_LIST_ICON_IMG 时用) */ + int icon_kind; /* MEOW_LIST_ICON_*(NONE=无图标) */ + const char *title; /* 主文本(必填,NULL 视为空行) */ + const char *subtitle; /* 副文本,可空 */ + int right; /* MEOW_LIST_RIGHT_* 右侧操作类型 */ + int on; /* right==SWITCH 时开关态(0/1) */ + int enabled; /* 1=可选中/可交互;0=变淡不响应命中 */ +} meow_list_item; + +/* 行数据源回调:idx 有效时填充 out 并返回 1;越界返回 0。每行轻量填充(不做 IO,§5.5)。 */ +typedef int (*meow_list2_source)(int idx, meow_list_item *out, void *ctx); + +/* 创建:视口(x,y,w,h) + 行数 count + 行数据源 + 选中模式。行高默认 56(可用 set_item_h 改)。 */ +meow_widget *meow_listview_create(int x, int y, int w, int h, int count, + meow_list2_source src, void *ctx, meow_list_sel mode); +/* 行高(固定;越界/<=0 忽略)。 */ +int meow_listview_set_item_h(meow_widget *lv, int item); +/* 行数(搜索过滤/懒加载/目录切换时动态更新):截断 sel 到新范围 + clamp scroll。 */ +void meow_listview_set_count(meow_widget *lv, int count); +/* 滚动:set 越界 clamp 到 [0, max_scroll];by 相对滚动(App on_swipe/touch_move 用)。 */ +void meow_listview_set_scroll(meow_widget *lv, int offset); +void meow_listview_scroll_by(meow_widget *lv, int dy); +int meow_listview_scroll(meow_widget *lv); +int meow_listview_max_scroll(meow_widget *lv); /* max(0, count*item_h - viewport_h) */ +/* 选中集(mode 语义): + * NONE :忽略;SINGLE:set_sel(idx) 设/清(idx<0 清除);MULTI:set_sel(idx) 翻转 idx。 + * get_sel 把当前选中下标写入 out[0..max),返回写入数;sel_count 返回选中总数。 */ +void meow_listview_set_sel(meow_widget *lv, int idx); +int meow_listview_get_sel(meow_widget *lv, int *out, int max); +int meow_listview_sel_count(meow_widget *lv); +/* 命中:返回行号(含滚动换算;禁用行/越界返回 -1);行右 1/3 = 右侧操作区(另判)。 */ +int meow_listview_hit(meow_widget *lv, int x, int y); +int meow_listview_hit_op(meow_widget *lv, int x, int y); /* 1=点在行右 1/3 操作区 */ +/* 行点击回调(idx=行号;op:0=行体,1=右侧操作区)。 */ +typedef void (*meow_listview_tap_fn)(int idx, int op, void *ctx); +void meow_listview_set_on_tap(meow_widget *lv, meow_listview_tap_fn cb, void *ctx); +/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1。 */ +int meow_listview_tap_at(meow_widget *lv, int x, int y); + /* ============================================================ */ /* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet * 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构),控件化。 diff --git a/meow_ui_widget.c b/meow_ui_widget.c index 21b428b..b3aa381 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -14,7 +14,8 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6, W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10, W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13, - W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17 }; + W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17, + W_LISTVIEW = 18 }; /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */ /* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */ #define MEOW_LAYOUT_MAX_CHILD 32 @@ -81,6 +82,14 @@ struct meow_widget { /* M2.71-bug8:W_LIST 行点击回调(可选)。 */ meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */ void *tap_ctx; /* 回调 ctx */ + + /* §5.5 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作+选中(calloc 归零,安全) */ + meow_list2_source src2; /* 行数据源(meow_list_item) */ + int sel_mode; /* MEOW_LIST_SEL_* */ + uint32_t *sel_bits; /* 多选位图(sel_bits_n 个字;SINGLE 不分配) */ + int sel_bits_n; /* 位图字数 */ + meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */ + void *lv_tap_ctx; /* 回调 ctx */ }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- @@ -328,6 +337,7 @@ void meow_widget_free(meow_widget *w) if (!w) return; meow_draw_widget_unregister(w); /* §6.2:绘制物注册表自动注销(stale id 失效勿用) */ live_btn_unregister(w); + if (w->sel_bits) { free(w->sel_bits); w->sel_bits = NULL; } /* W_LISTVIEW 多选位图 */ free(w); } @@ -881,6 +891,271 @@ int meow_list_tap_at(meow_widget *w, int x, int y) return idx; } +/* ============================================================ */ +/* §5.5 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作+选中 + * ------------------------------------------------------------ + * 行数据源(meow_list2_source)逐行填充 meow_list_item;容器只画可见行(虚拟化)+ 视口裁剪 + * (M2.73 纪律:只画视口内行/分隔线/文本,无标题栏/底部泄漏);局部 dirty;行高固定;首/末行 + * 钳制。颜色全部取 meow_theme_get() 语义色(日/夜/style 自动),禁止硬编码。 */ +/* ============================================================ */ +#define MEOW_LISTV_ICON 24 /* 图标盒尺寸(px) */ +#define MEOW_LISTV_PAD 16 /* 行内左右 padding(px) */ +#define MEOW_LISTV_SUB_GAP 2 /* 标题与副标题间隙(px) */ + +/* meow_ui_img_scaled 弱引用:W_LISTVIEW 的 MEOW_LIST_ICON_IMG 分支用它绘制图片图标。 + * 弱引用让「只用内置符号图标、不解码图片」的 App 不被牵连进 meow_ui_img.o/stb_image.o + * (静态库惰性链接:无强引用则不拉入 stb 解码器,避免所有 App 无谓膨胀 ~77KB)。 + * 宿主/设备需图片图标的 App 经 meow_ui_img_load_* 强引用 meow_ui_img.o → 此处自动解析。 */ +extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attribute__((weak)); + +static int listv_item_h(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 56; } + +/* 文本截断:超出 maxw 加 "..."(复用 browser_text_trunc 的逐字符宽度累加)。 */ +static void listv_text_trunc(int x, int y, const char *s, int maxw, int size, uint32_t col) +{ + if (!s) return; + int full = meow_ui_text_width(s, size); + if (full <= maxw) { meow_ui_text(x, y, s, size, col); return; } + const char *dots = "..."; + int dw = meow_ui_text_width(dots, size); + char tmp[128]; + int i; + for (i = 0; s[i] && i < (int)sizeof tmp - 4; i++) { + tmp[i] = s[i]; + tmp[i + 1] = '\0'; + if (meow_ui_text_width(tmp, size) > maxw - dw) break; + } + memcpy(tmp + i, dots, 4); + meow_ui_text(x, y, tmp, size, col); +} + +/* 行图标绘制(内置符号 / meow_img)。col=图标前景(选中/未选中由调用方定)。 */ +static void listv_draw_icon(const meow_theme *th, int x, int y, int kind, + const meow_img *img, uint32_t col) +{ + (void)th; + int s = MEOW_LISTV_ICON; + if (kind == MEOW_LIST_ICON_IMG && img && meow_ui_img_scaled) { /* 弱符号非空才画(未链 stb 则跳过) */ + meow_ui_img_scaled(x, y, s, s, (meow_img *)img); /* img 为 opaque 句柄,const 丢弃 */ + return; + } + if (kind == MEOW_LIST_ICON_FOLDER) { + meow_ui_fill_rect(x, y + 6, s, s - 6, col); /* 主体 */ + meow_ui_fill_rect(x, y + 2, 12, 6, col); /* 页签 */ + } else if (kind == MEOW_LIST_ICON_FILE) { + meow_ui_draw_rect(x + 4, y + 2, s - 8, s - 4, col);/* 页体 */ + meow_ui_fill_rect(x + 4, y + 2, 7, 4, col); /* 文件名横条 */ + } +} + +/* 行右侧操作区绘制(箭头/开关/复选/⋯)。x=动作槽左缘;col=前景。 */ +static void listv_draw_right(const meow_theme *th, int x, int y, int row_h, + int right, int on, uint32_t col) +{ + switch (right) { + case MEOW_LIST_RIGHT_ARROW: { + int cx = x + 4, cy = y + row_h / 2; + meow_gfx_line(cx - 4, cy - 6, cx + 4, cy, 2, col); + meow_gfx_line(cx + 4, cy, cx - 4, cy + 6, 2, col); + break; + } + case MEOW_LIST_RIGHT_SWITCH: { + int sw = 36, sh = 20; + int sx = x, sy = y + (row_h - sh) / 2; + meow_gfx_fill_round(sx, sy, sw, sh, sh / 2, on ? th->primary : th->onSurfaceVariant); + int kr = sh / 2 - 3; + int kx = on ? sx + sw - kr * 2 - 3 : sx + 3; + meow_gfx_fill_round(kx, sy + 3, kr * 2, kr * 2, kr, on ? th->onPrimary : th->surface); + break; + } + case MEOW_LIST_RIGHT_CHECK: { + int cx = x + 2, cy = y + row_h / 2; + meow_gfx_line(cx - 6, cy + 2, cx - 1, cy + 7, 2, col); + meow_gfx_line(cx - 1, cy + 7, cx + 7, cy - 6, 2, col); + break; + } + case MEOW_LIST_RIGHT_MORE: { + int cx = x; + int cy0 = y + row_h / 2 - 8; + for (int d = 0; d < 3; d++) + meow_ui_fill_rect(cx, cy0 + d * 6, 4, 4, col); + break; + } + default: break; + } +} + +/* 多选位图扩容(MULTI 且 idx 超出当前容量时;SINGLE 不分配)。 */ +static void listv_bitmap_grow(meow_widget *w, int idx) +{ + int need = (idx + 1 + 31) / 32; + if (need <= w->sel_bits_n) return; + uint32_t *nb = (uint32_t *)calloc((size_t)need, sizeof(uint32_t)); + if (!nb) return; + if (w->sel_bits && w->sel_bits_n) + memcpy(nb, w->sel_bits, (size_t)w->sel_bits_n * sizeof(uint32_t)); + free(w->sel_bits); + w->sel_bits = nb; + w->sel_bits_n = need; +} + +meow_widget *meow_listview_create(int x, int y, int w, int h, int count, + meow_list2_source src, void *ctx, meow_list_sel mode) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_LISTVIEW; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); + t->state = MEOW_WS_ENABLED; + t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx; + t->sel = -1; t->item_h = 56; t->scroll = 0; + t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE; + if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) { + t->sel_bits_n = (t->count + 31) / 32; + t->sel_bits = (uint32_t *)calloc((size_t)t->sel_bits_n, sizeof(uint32_t)); + } + meow_draw_widget_register(t); + return t; +} + +int meow_listview_set_item_h(meow_widget *w, int item) +{ + if (!w || w->type != W_LISTVIEW) return -1; + if (item > 0) { w->item_h = item; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); } + return w->item_h; +} +void meow_listview_set_count(meow_widget *w, int count) +{ + if (!w || w->type != W_LISTVIEW) return; + if (count < 0) count = 0; + if (count == w->count) return; + w->count = count; + if (w->sel >= w->count) w->sel = -1; + if (w->sel_mode == MEOW_LIST_SEL_MULTI) { /* 重分配多选位图 */ + free(w->sel_bits); w->sel_bits = NULL; w->sel_bits_n = 0; + int need = (count + 31) / 32; + if (need > 0) { + w->sel_bits_n = need; + w->sel_bits = (uint32_t *)calloc((size_t)need, sizeof(uint32_t)); + } + } + int ms = meow_listview_max_scroll(w); /* 行数减少时 clamp scroll */ + if (w->scroll > ms) w->scroll = ms; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} + +int meow_listview_max_scroll(meow_widget *w) +{ + if (!w || w->type != W_LISTVIEW) return 0; + int ms = w->count * listv_item_h(w) - w->h; + return ms < 0 ? 0 : ms; +} + +void meow_listview_set_scroll(meow_widget *w, int offset) +{ + if (!w || w->type != W_LISTVIEW) return; + int ms = meow_listview_max_scroll(w); + if (offset < 0) offset = 0; + if (offset > ms) offset = ms; + if (offset == w->scroll) return; + w->scroll = offset; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +void meow_listview_scroll_by(meow_widget *w, int dy) { if (w) meow_listview_set_scroll(w, w->scroll + dy); } +int meow_listview_scroll(meow_widget *w) { return (w && w->type == W_LISTVIEW) ? w->scroll : 0; } + +void meow_listview_set_sel(meow_widget *w, int idx) +{ + if (!w || w->type != W_LISTVIEW) return; + if (w->sel_mode == MEOW_LIST_SEL_NONE) return; + if (w->count <= 0) { w->sel = -1; return; } + if (idx >= w->count) idx = w->count - 1; /* 越界 clamp(§5.5 首/末行钳制) */ + if (w->sel_mode == MEOW_LIST_SEL_SINGLE) { + if (idx == w->sel) return; + w->sel = idx; /* idx<0 = 清除 */ + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + } else { /* MULTI:翻转 idx */ + if (idx < 0) { + if (w->sel_bits) memset(w->sel_bits, 0, (size_t)w->sel_bits_n * sizeof(uint32_t)); + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + return; + } + listv_bitmap_grow(w, idx); + if (!w->sel_bits) return; + w->sel_bits[idx >> 5] ^= (1u << (idx & 31)); + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); + } +} +int meow_listview_get_sel(meow_widget *w, int *out, int max) +{ + if (!w || w->type != W_LISTVIEW) return 0; + if (!out || max <= 0) return 0; + int n = 0; + if (w->sel_mode == MEOW_LIST_SEL_MULTI) { + for (int i = 0; i < w->count && n < max; i++) + if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) out[n++] = i; + } else if (w->sel >= 0 && n < max) { + out[n++] = w->sel; + } + return n; +} +int meow_listview_sel_count(meow_widget *w) +{ + if (!w || w->type != W_LISTVIEW) return 0; + if (w->sel_mode == MEOW_LIST_SEL_MULTI) { + int n = 0; + for (int i = 0; i < w->count; i++) + if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) n++; + return n; + } + return (w->sel >= 0) ? 1 : 0; +} + +/* 命中:返回行号(含滚动换算;禁用行/越界 -1)。抬手用最新坐标(goodix 免疫,调用方传抬手落点)。 */ +int meow_listview_hit(meow_widget *w, int x, int y) +{ + if (!w || w->type != W_LISTVIEW) return -1; + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1; + int item = listv_item_h(w); + int idx = (y - w->y + w->scroll) / item; + if (idx < 0 || idx >= w->count) return -1; + if (w->src2) { /* 禁用行不响应(§5.5) */ + meow_list_item it; + memset(&it, 0, sizeof it); + if (!w->src2(idx, &it, w->ctx)) return -1; + if (!it.enabled) return -1; + } + return idx; +} +int meow_listview_hit_op(meow_widget *w, int x, int y) +{ + int idx = meow_listview_hit(w, x, y); + if (idx < 0) return 0; + int opx = w->x + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5) */ + if (x < opx) return 0; + if (w->src2) { + meow_list_item it; + memset(&it, 0, sizeof it); + if (!w->src2(idx, &it, w->ctx)) return 0; + if (it.right == MEOW_LIST_RIGHT_NONE) return 0; + } + return 1; +} +void meow_listview_set_on_tap(meow_widget *w, meow_listview_tap_fn cb, void *ctx) +{ + if (!w || w->type != W_LISTVIEW) return; + w->lv_tap_cb = cb; w->lv_tap_ctx = ctx; +} +int meow_listview_tap_at(meow_widget *w, int x, int y) +{ + int idx = meow_listview_hit(w, x, y); + if (idx < 0) return -1; + int op = meow_listview_hit_op(w, x, y); + meow_listview_set_sel(w, idx); + if (w->lv_tap_cb) w->lv_tap_cb(idx, op, w->lv_tap_ctx); + return idx; +} + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -1071,6 +1346,84 @@ void meow_widget_draw(meow_widget *w) if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } + case W_LISTVIEW: { + /* §5.5 结构化列表(纵向 LazyColumn):只画可见行(虚拟化)+ 视口裁剪(M2.73 纪律)。 + * 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer / + * onPrimaryContainer(§5.8.3);禁用行变淡不响应(命中由 hit 层保证)。全语义色。 */ + int item = listv_item_h(w); + int vp = w->h; /* 纵向 */ + int off = w->scroll; + int maxs = w->count * item - vp; + if (maxs < 0) maxs = 0; + if (off < 0) off = 0; + if (off > maxs) off = maxs; + int first = (item > 0) ? off / item : 0; + int last = (item > 0) ? (off + vp) / item + 1 : 0; + if (first < 0) first = 0; + if (last > w->count) last = w->count; + int vx0 = w->x, vy0 = w->y, vy1 = w->y + w->h; + int thh = meow_ui_text_height(th->font_body); + int pad = MEOW_LISTV_PAD, isz = MEOW_LISTV_ICON; + int text_x = vx0 + pad + isz + pad; + int op_start = vx0 + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5 命中) */ + int text_maxw = op_start - pad - text_x; /* 标题/副标题可用宽(不压操作区) */ + if (text_maxw < 8) text_maxw = 8; + for (int i = first; i < last; i++) { + meow_list_item it; + memset(&it, 0, sizeof it); + if (!w->src2 || !w->src2(i, &it, w->ctx)) continue; + int ry = w->y + (i * item - off); + if (ry >= vy1) break; + if (ry + item <= vy0) continue; + int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */ + int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */ + int sel = (i == w->sel); + uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface; + uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant; + if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer); + + /* 图标(左) */ + uint32_t iccol = sel ? th->onPrimaryContainer + : ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary + : th->onSurfaceVariant); + int icy = ry + (item - isz) / 2; + if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */ + listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol); + + /* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */ + int ty; + if (it.subtitle && it.subtitle[0]) { + ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2; + if (it.title && ty >= vy0 && ty + thh <= vy1) + listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol); + int sy = ty + thh + MEOW_LISTV_SUB_GAP; + if (sy >= vy0 && sy + thh <= vy1) + listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol); + } else { + ty = ry + (item - thh) / 2; + if (it.title && ty >= vy0 && ty + thh <= vy1) + listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol); + } + + /* 右侧操作区 */ + if (it.right != MEOW_LIST_RIGHT_NONE) { + int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */ + if (ax >= op_start) + listv_draw_right(th, ax, ry, item, it.right, it.on, subcol); + } + + /* 分隔线(仅落在可见带内才画) */ + int sy = ry + item - 1; + if (sy >= cl && sy < cr) + meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant); + + /* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */ + if (!it.enabled) + meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90); + } + if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + break; + } case W_CUSTOM: /* SDK 不替自定义画布画背景/状态层;App 在 render_cb 里自绘(th 供取色), * 可读 meow_widget_state(状态位)/ meow_widget_opts_get(通用修饰)自行叠。 */ diff --git a/test_ime_fix.c b/test_ime_fix.c index 74f5891..dd9593e 100644 --- a/test_ime_fix.c +++ b/test_ime_fix.c @@ -8,8 +8,9 @@ * gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_draw.c meow_ui_view.c \ - * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + * meow_ui_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm) */ #include "meow_ui.h" #include "meow_ui_internal.h" diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c index 18d4c59..e111176 100644 --- a/test_meow_ui_widgets.c +++ b/test_meow_ui_widgets.c @@ -5,16 +5,74 @@ * gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \ * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ - * meow_ui_draw.c meow_ui_view.c \ - * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + * meow_ui_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm * 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕) + * + * 注:W_LISTVIEW 的 MEOW_LIST_ICON_IMG 分支调用 meow_ui_img_scaled → 宿主链 meow_ui_img.c+stb_image.c+-lm。 + * 结构化列表断言用 back_buf 像素(region_has/px_near),画完即读,非目检。 */ #include "meow_ui.h" +#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */ #include #include #include #include +/* ---- back_buf 像素读取(断言真实渲染:画完读像素,不依赖目检) ---- */ +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; } + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } +static int px_near(int x, int y, int r, int g, int b) +{ + int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb); + return near2(rr, r) && near2(gg, g) && near2(bb, b); +} +/* 区域内含「接近 target 色」的像素数(>0 即该色被真实绘出)。 */ +static int region_has(int x, int y, int w, int h, int r, int g, int b) +{ + int cnt = 0; + for (int dy = 0; dy < h; dy++) + for (int dx = 0; dx < w; dx++) + if (px_near(x + dx, y + dy, r, g, b)) cnt++; + return cnt; +} +#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0) + +/* 结构化列表(W_LISTVIEW)测试数据源 */ +typedef struct { const char *title; const char *sub; int ik; int right; int enabled; } lvrow_t; +static lvrow_t g_lvrows[4] = { + { "Row A", "sub A", MEOW_LIST_ICON_FOLDER, MEOW_LIST_RIGHT_MORE, 1 }, + { "Row B", "sub B", MEOW_LIST_ICON_FILE, MEOW_LIST_RIGHT_NONE, 1 }, + { "Row C", "sub C", MEOW_LIST_ICON_NONE, MEOW_LIST_RIGHT_ARROW, 0 }, /* 禁用 */ + { "Row D", "sub D", MEOW_LIST_ICON_FOLDER, MEOW_LIST_RIGHT_CHECK, 1 }, +}; +static int lv_src(int idx, meow_list_item *out, void *ctx) +{ + (void)ctx; + if (idx < 0 || idx >= 4) return 0; + out->icon_kind = g_lvrows[idx].ik; + out->title = g_lvrows[idx].title; + out->subtitle = g_lvrows[idx].sub; + out->right = g_lvrows[idx].right; + out->on = 1; + out->enabled = g_lvrows[idx].enabled; + return 1; +} + /* 测试数据源(列表容器) */ static int list_src(int idx, char *label, int len, void *ctx) { @@ -158,6 +216,75 @@ int main(void) assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60)); printf("PASS lazy list: scroll clamp/virtualize/hit_index/tap_at/select (col+row)\n"); + /* ---------- 结构化列表(W_LISTVIEW):行渲染像素 + 命中 + 滚动裁剪 ---------- + * lv 位于 (20,240,320,112)item_h=56 → 行0 y240..296、行1 296..352。 + * 断言全部「画完读 back_buf 像素」(region_has/px_near),非目检。 */ + meow_widget *lv = W(meow_listview_create(20, 240, 320, 112, 4, lv_src, NULL, MEOW_LIST_SEL_SINGLE)); + assert(meow_listview_set_item_h(lv, 56) == 56); + const meow_theme *lth = meow_theme_get(); + int lvx = 20, lvy = 240, lvw = 320, lvh = 112, itemh = 56; + int textx = lvx + 16 + 24 + 16; /* 文本起 x = 76 */ + int op_start = lvx + (lvw * 2 / 3); /* 行右 1/3 操作区起 = 20+213=233 */ + int sb_r, sb_g, sb_b, os_r, os_g, os_b, pc_r, pc_g, pc_b; + int ospv_r, ospv_g, ospv_b, prim_r, prim_g, prim_b; + UNPK(lth->bg, sb_r, sb_g, sb_b); + UNPK(lth->onSurface, os_r, os_g, os_b); + UNPK(lth->primaryContainer, pc_r, pc_g, pc_b); + UNPK(lth->onSurfaceVariant, ospv_r, ospv_g, ospv_b); + UNPK(lth->primary, prim_r, prim_g, prim_b); + int thh = meow_ui_text_height(lth->font_body); /* 20(宿主 5x7) */ + + /* 行 0 的标题/副标题 y(含 subtitle 的两行居中):ty=240+(56-42)/2=247 */ + int r0_ty = lvy + (itemh - (2 * thh + 2)) / 2; + int r0_sy = r0_ty + thh + 2; + + /* 1) 行内容渲染:清底 + 只画列表,断言图标/标题/副标题/右侧 ⋯ 真实像素 */ + meow_ui_clear(lth->bg); + meow_widget_draw(lv); + assert(region_has(lvx + 16, lvy + (itemh - 24) / 2, 24, 24, prim_r, prim_g, prim_b) > 100); + assert(region_has(textx, r0_ty, 80, thh, os_r, os_g, os_b) > 2); + assert(region_has(textx, r0_sy, 80, thh, ospv_r, ospv_g, ospv_b) > 2); + assert(region_has(lvx + lvw - 16 - 24, lvy + (itemh - 20) / 2, 8, 20, ospv_r, ospv_g, ospv_b) > 20); + printf("PASS listview: row render (icon/title/subtitle/right-more) pixels\n"); + + /* 2) 选中态:行 1 → primaryContainer 高亮;行 0 无高亮(bg) */ + meow_listview_set_sel(lv, 1); + meow_ui_clear(lth->bg); + meow_widget_draw(lv); + assert(px_near(28, lvy + itemh + 48, pc_r, pc_g, pc_b)); /* 行1带底部空白 = primaryContainer */ + assert(px_near(28, lvy + 48, sb_r, sb_g, sb_b)); /* 行0带空白 = bg(未选中无高亮) */ + int selout[4]; + assert(meow_listview_get_sel(lv, selout, 4) == 1 && selout[0] == 1); + assert(meow_listview_sel_count(lv) == 1); + printf("PASS listview: selected row highlight + get_sel/sel_count\n"); + + /* 3) 命中:行号 + 右侧操作区分 + 禁用行不命中 */ + assert(meow_listview_hit(lv, 28, lvy + 20) == 0); /* 行0 体 */ + assert(meow_listview_hit_op(lv, 28, lvy + 20) == 0); /* 体非操作区 */ + assert(meow_listview_hit_op(lv, op_start + 10, lvy + 20) == 1); /* 行0 右1/3=操作区 */ + meow_listview_set_sel(lv, -1); + /* 滚动到 3、4 行行内命中(scroll=112 → 行2 y240、行3 y296) */ + meow_listview_set_scroll(lv, 112); + assert(meow_listview_scroll(lv) == 112); + assert(meow_listview_max_scroll(lv) == 4 * 56 - 112); + assert(meow_listview_hit(lv, 28, lvy + 28) == -1); /* 行2 禁用 → 不命中 */ + assert(meow_listview_hit(lv, 28, lvy + itemh + 28) == 3); /* 行3 命中 */ + printf("PASS listview: hit row/hit_op/disabled-not-hit\n"); + + /* 4) 滚动视口裁剪:滚到 112 后画,视口外(上缘/下缘)无泄漏(保持 bg) */ + meow_ui_clear(lth->bg); + meow_widget_draw(lv); + assert(px_near(21, lvy - 1, sb_r, sb_g, sb_b)); /* 视口上缘外 = bg(无上漏) */ + assert(px_near(21, lvy + lvh, sb_r, sb_g, sb_b)); /* 视口下缘外 = bg(无下漏) */ + /* 视口内 行3 标题真实渲染(行3 y=296,标题 y=303) */ + assert(region_has(textx, lvy + itemh + (itemh - (2 * thh + 2)) / 2, + 80, thh, os_r, os_g, os_b) > 2); + /* 行 2 禁用行「变淡」:draw 后其左 padding 像素应被 dim 覆盖(偏离纯 bg) */ + assert(!px_near(28, lvy + 28, sb_r, sb_g, sb_b)); + printf("PASS listview: scroll clip no-leak + disabled-row dim\n"); + meow_listview_set_scroll(lv, 0); + meow_listview_set_sel(lv, -1); + /* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */ meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one"); const char *items[] = { "Alpha", "Beta", "Gamma" }; From 5d4412835799460dffdd7f2219af77a28c22c54d Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 05:04:26 +0800 Subject: [PATCH 27/49] =?UTF-8?q?feat(sdk):=20=E9=80=9A=E7=94=A8=E9=95=BF?= =?UTF-8?q?=E6=8C=89=E4=BA=8B=E4=BB=B6=20(agent-191,=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=AE=9A=E6=A1=88)=20=E2=80=94=20MEOW=5FEV=5FLONG=5FPRESS=20?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E5=BC=8F=E4=BA=8B=E4=BB=B6+meow=5Fui=5Fset?= =?UTF-8?q?=5Flong=5Fpress=5Fcb/ms(opt-in,=E9=BB=98=E8=AE=A4500ms,?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8>12px=E5=8F=96=E6=B6=88,=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E5=90=8E=E6=8A=AC=E6=89=8B=E6=8A=91=E5=88=B6TAP/SWIPE);=20?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=8C=89=E4=BD=8F=E8=A7=86=E8=A7=89=E9=98=88?= =?UTF-8?q?=E5=80=BC=E5=90=8C=E6=BA=90;=20files=20=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E4=BB=8EApp=E5=B1=82=E8=BF=81=E5=85=A5=20SDK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a 4335c33a→6de89c27; hello→72fb20da; files→b1c4f58f 宿主 widgets(含6条长按断言)/p1p2p3/ime_fix/test_fsx 全 PASS; 未改 meow_ui_events 结构(防 -Wmissing-field-initializers 破坏既有App) --- meow_ui.h | 22 +++++++++++++ meow_ui_input.c | 70 ++++++++++++++++++++++++++++++++++++++--- meow_ui_internal.h | 10 ++++++ meow_ui_widget.c | 10 +++--- test_ime_fix | Bin 0 -> 304304 bytes test_meow_ui_widgets | Bin 0 -> 303960 bytes test_meow_ui_widgets.c | 60 +++++++++++++++++++++++++++++++++++ test_p1p2p3_repro | Bin 0 -> 379192 bytes 8 files changed, 163 insertions(+), 9 deletions(-) create mode 100755 test_ime_fix create mode 100755 test_meow_ui_widgets create mode 100755 test_p1p2p3_repro diff --git a/meow_ui.h b/meow_ui.h index b150aef..d2a2f7b 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -173,6 +173,28 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/ * x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */ void meow_ui_swipe_hint(int x); +/* ============================================================ */ +/* §6.3 通用长按(2026-08-31) */ +/* ============================================================ */ +/* 控件按下 ≥ 阈值(默认 500ms,可配)未抬手、且按下后位移 ≤ 容忍(超移动/抬手取消) + * → 触发一次长按回调;坐标 = 触发时刻的按下点(App 据此 meow_widget_hit / meow_listview_hit + * 判定命中哪个控件/行,与 on_tap 同一套命中流程)。 + * + * **opt-in**:注册回调(meow_ui_set_long_press_cb)才启用长按判定;未注册时行为与之前完全一致 + * (按压不触发长按、抬手照常 TAP/SWIPE)——hello 等不使用长按的 App 零回归。 + * + * 与 pressed 状态机共存:按压视觉态(MEOW_WS_PRESSED / 按住加深)不变;短按(未达阈值或 + * 抬手先于阈值)仍正常触发 on_tap 点击;长按已触发后其抬手**不再产生 TAP/SWIPE**(SDK 抑制 + * 点击,App 无需自行吞掉后续 tap)。 + * + * 注册经独立 set 接口:**不做进 meow_ui_events**——避免给既有 App(hello/files)按位置初始化 + * meow_ui_events 的写法在 -Wextra 下引入「缺省成员」告警(_missing-field-initializers), + * 保持 SDK 头向后兼容。 */ +typedef void (*meow_long_press_fn)(int x, int y); +void meow_ui_set_long_press_cb(meow_long_press_fn cb); /* 注册/更换回调;NULL=取消 */ +void meow_ui_set_long_press_ms(int ms); /* 长按阈值(ms);<=0 恢复默认 500 */ +int meow_ui_long_press_ms(void); /* 当前阈值(默认 500) */ + /* ============================================================ */ /* §6.1 控件生命周期与归属(Activity 栈 + 三层控件模型) [源:2026-08-30] */ /* ============================================================ */ diff --git a/meow_ui_input.c b/meow_ui_input.c index c19d3e2..1629eb7 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -35,6 +35,8 @@ #define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ +#define MEOW_LONG_PRESS_MS 500u /* §6.3 通用长按默认阈值(ms;meow_ui_set_long_press_ms 可配) */ +#define MEOW_LONG_PRESS_SLOP 12 /* §6.3 按下后「仍算按住」的最大位移(px);超此取消长按 */ #define QUEUE_N 128 #define MAX_DEVS 8 #define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */ @@ -129,6 +131,13 @@ static int t_origin_valid = 0; /* 原点是否已锁定 */ static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ /* M2.71-bug2/6:拖动过程事件上次上报坐标(坐标变化才入队 MEOW_EV_TOUCH_MOVE,防刷屏)。 */ static int t_mv_x = -1, t_mv_y = -1; +/* §6.3 通用长按:本次按压是否已触发过长按(触发后抬手不再产生 TAP/SWIPE)。 */ +static int t_long_fired = 0; + +/* §6.3 通用长按:阈值(默认 MEOW_LONG_PRESS_MS;App 可经 meow_ui_set_long_press_ms 配)。 */ +static int g_long_press_ms = MEOW_LONG_PRESS_MS; +/* §6.3 注册的长按回调(NULL=未注册;触发时以 (x,y) 调用,坐标=触发时刻按下点)。 */ +static meow_long_press_fn g_long_press_cb = NULL; /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -142,6 +151,7 @@ static void touch_press(int x, int y) t_dx = 0; t_dy = 0; t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */ + t_long_fired = 0; /* §6.3:每次新按压复位长按触发标记 */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 * M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE @@ -165,11 +175,18 @@ static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; + int was_long = t_long_fired; /* §6.3:本次按压是否已触发过长按(抬手不再产生点击/滑动) */ + t_long_fired = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; + /* 长按已触发:本次手势由长按消费,抬手不再产生 TAP/SWIPE/NOTIF_PULL(抑制点击,§6.3)。 */ + if (was_long) { + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; + return; + } /* 手势判定只在「抬手(release)边沿」做:按下/移动过程不触发 SWIPE,仅报 PROGRESS。 * * 返回手势("左滑"语义):原点确实在左缘 + 向右移动 ≥SWIPE_THRESH。设备实际为 @@ -227,17 +244,52 @@ unsigned int meow_input_touch_hold_ms(void) void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) { if (down) { - t_down = 1; t_press_valid = 1; t_origin_valid = 1; + t_down = 1; t_press_valid = 1; t_origin_valid = 1; t_long_fired = 0; t_cx = x; t_cy = y; t_dx = x; t_dy = y; uint64_t now = now_ms(); t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0; } else { - t_down = 0; t_press_valid = 0; t_origin_valid = 0; + t_down = 0; t_press_valid = 0; t_origin_valid = 0; t_long_fired = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; t_mv_x = -1; t_mv_y = -1; } } +/* 宿主单测/内部注入:模拟按下后移动到 (x,y)(保原点、更新当前坐标;位移超长按容忍即取消)。 + * 对应输入线程 touch_update 的位移分支(这里不产生语义事件,仅更新坐标状态)。 */ +void meow_input_touch_move(int x, int y) +{ + if (!t_down || !t_origin_valid) return; + t_cx = x; t_cy = y; + t_mv_x = x; t_mv_y = y; +} + +/* 宿主单测/内部注入:模拟一次抬手(真实 touch_release;含长按后抑制 TAP/SWIPE)。 */ +void meow_input_touch_release(void) { touch_release(); } + +/* ---- §6.3 通用长按公共 API + 阈值跨越检测 ---- + * meow_input_long_press_poll:主循环(meow_ui_run 每帧)与宿主单测调用。读触摸状态机: + * 按下、原点/坐标已就绪、未触发过长按、位移 ≤ 容忍、按住时长 ≥ 阈值 → 置 t_long_fired, + * 入队 MEOW_EV_LONG_PRESS(a=x,b=y=当前按下点),返回 1;否则返回 0。幂等(一次按压一次)。 */ +void meow_ui_set_long_press_cb(meow_long_press_fn cb) { g_long_press_cb = cb; } +void meow_ui_set_long_press_ms(int ms) +{ g_long_press_ms = (ms > 0) ? ms : (int)MEOW_LONG_PRESS_MS; } +int meow_ui_long_press_ms(void) { return g_long_press_ms; } + +int meow_input_long_press_poll(void) +{ + if (!g_long_press_cb) return 0; /* 未注册回调:长按整体关闭,保持旧点击语义(短按/长按都 TAP) */ + if (!t_down || t_long_fired || !t_origin_valid || t_cx < 0 || t_cy < 0) return 0; + int mdx = t_cx - t_dx, mdy = t_cy - t_dy; + int amx = mdx < 0 ? -mdx : mdx, amy = mdy < 0 ? -mdy : mdy; + int move = amx > amy ? amx : amy; + if (move > MEOW_LONG_PRESS_SLOP) return 0; /* 位移超容忍:取消 */ + if (meow_input_touch_hold_ms() < (unsigned)g_long_press_ms) return 0; /* 未达阈值 */ + t_long_fired = 1; + push_ev(MEOW_EV_LONG_PRESS, t_cx, t_cy); + return 1; +} + /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ static void *input_thread(void *arg) { @@ -338,8 +390,12 @@ void meow_ui_run(const meow_ui_events *ev) while (!g_exit) { /* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。 - * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */ - if (!meow_ime_active()) meow_widget_touch_refresh(); + * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 + * §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动;启动窗口不触发)。 */ + if (!meow_ime_active()) { + meow_widget_touch_refresh(); + if (now_ms() >= startup_cooldown_until) meow_input_long_press_poll(); + } meow_input_ev e; int got = 0; @@ -407,6 +463,12 @@ void meow_ui_run(const meow_ui_events *ev) if (g_events_set && g_events.on_swipe_progress) g_events.on_swipe_progress(x0, dx); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); + } else if (e.type == MEOW_EV_LONG_PRESS) { + /* §6.3 通用长按:按下 ≥ 阈值未抬手触发。坐标=触发时刻按下点(App 用 + * meow_widget_hit/meow_listview_hit 判定命中控件/行)。已注册回调则分发; + * 其抬手已被 touch_release 抑制(不再产生 TAP/SWIPE),此处无需吞后续 tap。 */ + if (in_startup) continue; + if (g_long_press_cb) g_long_press_cb(e.a, e.b); } } diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 9e7fc22..2dac50f 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -142,6 +142,7 @@ int meow_gfx_mfont_ready(void); #define MEOW_EV_SWIPE_PROGRESS 4 /* a=按下X(t_dx), b=当前X(t_cx);dx=b-a(>0=左缘向内划=返回手势) */ #define MEOW_EV_TOUCH_DOWN 6 /* a=x, b=y:按下后首个有效坐标(拖动起点,M2.71-bug2/6) */ #define MEOW_EV_TOUCH_MOVE 7 /* a=x, b=y:拖动过程坐标变化(坐标变才入队;跟手用) */ +#define MEOW_EV_LONG_PRESS 8 /* a=x, b=y:按下 ≥ 阈值未抬手(长按触发;其抬手不再产生 TAP/SWIPE) */ typedef struct meow_input_ev { uint8_t type; @@ -175,6 +176,15 @@ unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒 * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); +/* ---- 通用长按(§6.3,2026-08-31)---- + * 主循环/宿主调用:检测「按下 ≥ 阈值且位移 ≤ 容忍」的跨越;触发则入队 MEOW_EV_LONG_PRESS + * 并返回 1(幂等:一次按压只触发一次)。供 meow_ui_run 每帧调用与宿主单测模拟时序。 */ +int meow_input_long_press_poll(void); +/* 宿主单测/内部注入:模拟一次抬手(真实 touch_release;含长按后抑制 TAP/SWIPE)。设备运行时无调用方。 */ +void meow_input_touch_release(void); +/* 宿主单测/内部注入:模拟按下后移动到 (x,y)(保原点、更新当前坐标;位移超长按容忍即取消)。设备运行时无调用方。 */ +void meow_input_touch_move(int x, int y); + /* ---- 控件层实时刷新(meow_ui_widget.c)---- * 每帧由 meow_ui_run 调用(主线程):实时触摸按下/释放/按住达阈值时,重绘命中的按钮区 * 并局部上屏,让按压/按住/释放视觉即时反映,无需 app 在 on_tick/on_tap 重绘(agent-119)。 diff --git a/meow_ui_widget.c b/meow_ui_widget.c index b3aa381..e3fc97b 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -23,9 +23,9 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, /* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */ #define MEOW_CONTAINER_MAX 8 -/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 - * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ -#define MEOW_BTN_HOLD_MS 500u +/* 按钮「按住」判定阈值:视觉上区分「短按按下」与「按住」(更深状态层)。 + * 阈值取 meow_ui_long_press_ms()(§6.3 通用长按默认 500ms,App 可配)——与长按事件同源, + * 避免「长按已触发但视觉未加深」的不一致。触发语义不变(on_tap 仍按抬手判定)。 */ /* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。 * 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */ @@ -249,7 +249,7 @@ void meow_widget_touch_refresh(void) } int down = meow_input_touch_down(); meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL; - int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + int hold = down && hit && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms(); if (!down) { /* 释放:把最近按压按钮恢复为常态 */ @@ -1218,7 +1218,7 @@ void meow_widget_draw(meow_widget *w) meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live; - int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + int hold = live && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms(); if (disabled) press = 0; /* §5.8.6 五型:filled=primary/onPrimary;tonal=secondaryContainer/onSecondaryContainer; * outlined=透明底+primary 文+outline 描边;text=透明底+primary 文;elevated=surfaceContainerLow+primary 文。 diff --git a/test_ime_fix b/test_ime_fix new file mode 100755 index 0000000000000000000000000000000000000000..87d348af09d93885fe41d11ad9e25b68d2845eea GIT binary patch literal 304304 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�z1Zc~>3%-3cC`ks*Y7W> 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

c7)@ZTfIh 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 literal 0 HcmV?d00001 diff --git a/test_meow_ui_widgets b/test_meow_ui_widgets new file mode 100755 index 0000000000000000000000000000000000000000..6b0b860ecbc7f76280cd688722856dde1328a73e GIT binary patch literal 303960 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

    1J2l z_vv+TJY0+8h{>7aJHcUPL%%OgGrMehdA>3Rd?lV)o@pMkBWmPz#Ppito53TSH2F;N zWYVQa1{Yhmn8!+o{FD3;XdT>K=+OL4b(?e9;`Tv>j%{ZVZ_af(yB%3KOJ^&I9kgk? z{O*pf;oY~`xyH(T$Q3$%JHzbySJhi>D&^J0=X&DuY&)^-d^s?4|4zD%)TxvR!k2q2m|89v19Y z!`v5IZp9|fH(WPy>5-(J3l@Rb|$xyFwL80R|U~g5uJ8T>uUAiM*`Db8O zCY?izm;1h&awiqD#LcZ&fPYq@{poh5rT8Y_E;HM; zyF=|#akDSouGCVT7i3WwUOe&#Y#x1PC4)b8GWa=oUQ{glkf~T6b0-%|AIeuwxd0xS zH(FTU!A1%;-orW_tVJ;90wv~*ipOP*oy;9Yo0W^^Z$oS2dmsJ{-Y}LNG2JH@M?q)1 z{D!{g2q(`@p6Rxe#--ZkURBIWHrHT3*qqvR)oHgd-LCgl#or(gwX?dQ-BPtnk16SP zg%QQ)XeYUt{K(=1q_-2VCDW}PPNw&O=RwKzJ}=YK$YSZfeC4yios37>{6COy6VH?# zA-#VYIEV$xpZdCi`dx8aiue4e;uC?E(x~Dl(u^Mm4O_8;3#Sy{Pn?qf+Sr=|_gwrE z|6Bs@jiT=X#*YUW)6`d$?*p1MX$}Led#mI9=;EW`SN)Hu{@!`(bIxXM{YmNirP0L) zsh?TDtwU}87j5+XL7lcM(rvrP6z`#Jc}iil+Wg43{kCm;VNJger`zVn7T-%->0=*$ zruNwjp|>+r({6vdUH91HI@+}lc5R-aul>aSfMdi(HTrNeIz1($?o*1(!LeI9rQg}~ ztLHn_|JcL_Y}*y-w)s`(X8dus7Q`Vr1cz;D-gv}-k))p5JNbi3|x#i_KDeUv0C=^@7VJ9@yXIdetdW+U%46jx8WZe*GuF-CS9_wi~C$*=dO{>Ff;bJeSNAr~uYc)e3+A}irSDM9BxU~jm&1*`mhgs)p zEoU~hF5S21u8!S%s(MOvL|t^%qP?ZkC-N1o)3i6DJ&Ka{nG)Dg2h;vZfT_+BpT9h6 zcV!Io?UqI`wY$dWb5Xmu#W3G)c?46tl|H{LYIl7M^X*ngFtuCj^Q?sv{x-xg-|p53 zrgocsp7r*`QRv}z^0$M7wXEb?X5T=zp4rm6RB|+1GGnX_vwy)2v=-(hJ>~6r+s2j; zMK5=FoXGmZq2RY$POv&oG2FdVK3NhHFlcu#kXKr&s_tarQo@e{S|o1vi7sIvNv+C@Gwrsc(Tk5 zYw&bVC@vPBwcuF|p6eq#cfd1v-ctw9q%@xV#Nz9~!~UYRSMfNc+gxqsaMdy75cRtq z-_ZOx$U$+h^{QI>7Y^w6jN&-@wf@}OTKs0kudBR&20Q=O5T~O8Ix1^l%IUm?HPooX zE8%%LdsA=A+r6m*x=@VJ_|+EaU=rh9jqFOf;!E_W{aTA>33wKRXHgYT1=`Yh7N+rZ zw-uigo~FUYZNwh<-y?fEsQ41*JlosjxuA{r+tPUQlZyXGcxXFuu4|i%=zn-X{mI2A zsc&Uhnp}KO;Ds@PwcfO?8tcHdNVrmCxz+)-vN*H&*#_+&uG4;Dx_$Rq#lKbi1?<;O z#eXDIPlsqI*5PMG8b{~Z#a-ZFEJ|zBHcvl;Sqg zLjBw8)Xz>z@!mV7`0l{_x?0)&#_909xW7s1_Jyg%>u6tNmnl4b!jl@?7N+s!UsGHL z9;?sXwBkvk$;bJVz5VyT9yv(1&F1REDSPe4|E{QGui42deoNDeuMhOj^834WaJ{|` zu1RTJh3Um90T({mrg1Ea&3(Y~#O5rqnWv4-Hhx}HC+=xqnRs=z`c1{X73qHS=Q#OD z7RWj+3&nt8p8i;O`_p)O&n^D$B`@>BYm1*Ejs01hYmxnVTYL*?=_S)SqnM-oU#~(B zpR{|cdoy`cEG-&boHRWDSldwMmcz|s>^WBin@rsm{MnfBN#?D#9c#*tfj-%1_ury1 zqI*X10Q8h-w}Se%|J+RWaSI)f3D4it$KU5G^XlMQ$eMxr)$t}jv-l<9qW+cC7mvbc zdS@0NB+og9)_Uxx&g4uZU-^t+JM6lSS53~du)i1VUdGKGZe3^Ba`(D*T`r1Ked)e) z=M_J$zQOyIrH&6%IInm$u>2d%t|n;7@+X?a+ovxzy9yT{kv&Dmb#@U1{H6ErX+4Z@rJq4dehC* z)9v%Kir0xh=D?=#>f%uFh?Z^0A;W%}Xn7B`OcX8E^|@Owye36U?tWm%YRZDXYU2Yi^2JQXb|3Z;Z2PP52xGYE-ao$o9(O@ zv+QeWPe*(#0mlpcrQ1)Mmf{1Mw$c78zRxq$yx`wcmGu64G)^B#<4eEmzwaZS#y`B_IC@XFNAJ(m-XNP@;MlD` z;U(5ZU%E~2?BajWW)brsjsIKO_YnR6x!CObu-d2WW70V(nz4_kXn!Mh419q<2l^QE_u4WujXw7p^ZH_uKCfjg z`S@5%$6lT3T{{T0rE4*i%In>qtMm3r#f4*0M{8*a=y$SRycpOb;5bEJA1B_*$eAq&+pI>?g@-Ax3NF(`tbF_*%^C< zwyw_n*Zr1xf;~UYOw-y$>k~cG=V4lB2AcYxfDhJ~^Y<2 z!@l80=YBi6ah|}=4KVgLbe=AmFlX3d>sgo`Q+JxqQ*E0JYdl*>8a zKR?O$;qk@g*x|I?(B8jh&$3b)SI}8}1v`Ay{T*8ZUxs@b!Y#bnW{-n9qA#JHeB54TnhX9sMaTU*znS8BJd(9Iv>pTZ>QAAo zK#O28KGDN<*s-?U(`VDjgfgA~T6ufC&*ZEa8#}r=``A%zH}CyS`YEGZ#^r;h z$=r8fU6aF}4*e%|4xvoz9Nq2Vjt6$UBa?cXBky^})@k9+5p_Es_`t#5-+tj&`2+v) ztIn0r|0=(w_W|>A$1}_acc@D zH(S_GxQKf(SDi9C!_&e19hb4=N2`p!r&Y_ET>X)>@nw3}_(dysZd$nuGr^28Bkg^g zF*d&?>)HYrPFp_V7;VSedpEvq^Re4*$Db98*k|t}=7f7G&$e)ngShr%;!YWU(TUwh zDz`eD*@Jy4jh#9;b!1CF#n#I$z=_M6idr=Pw3Q5k+_Dj`d142#!t&Y_!8>(F z4xT7)k$iLA^gC~$4hG=z@)>{&In>}jQ+R2F0? zn)BQR3U(yf$G+XL9y(*ZOK50V4?VIrK7KuHE(4D-Psw-+y`o9?pcF?2`=`c0<*Q?x zyGxeL!%{Kk5PkGPkM&FV`~l~_Y9Hoe;r)THC2%*!1i0=XxcURf9{EIRh`VRV*yiRD z1vd{+n=qzW{>*5rvm~Y-a4{#zciVY%MfszfKU=*oHl6W+`NE;eCb`#V@r`u&EDckA zJmOpgyQlzrF%L|3qw?t?eSum3%_(MCUSofLdak1;>uE|KDdEqf8P z()LzI%L`+?orUodxO6ZLc$xa5-||6U;-if0psPh^JJx2gPt7;`ZY{XH_X*ug%yS2t zx;v0hrGKUAxm|Ru1?NhS<96t{-O;h+Ri2J8o&y&hcbyC!)Gr}ZONZnlIu>~x+rhCJ zIovRQDR);aZR$I=VS8qD2mMT7-obcRy1iiUWtXo>?EE&)rf%a-OHb<;o7SIRDbb4hPQyZ;0bV4o8N5;>4SX?5B3jSa(E~26oh$j3u#M3 z@KyX=buJ@5Teu&8^e#f1YGX&&=O@2_nmcXnh( z?oIA%@Qsv~wk<=FI~(>MfW;g6at+?9FTWzZ8mqvYrt=lkr?GDVy2Bp4&vQ?~=bg`2 z*VT(1Tt1(+pRA-GlhA{lyFyK=u+*Q)aYh9G-9{WJ0>m`PI0g-Yjec{>7f3|$}o+EvkNvpu(wsQF%)|e zPnK8B&!|_1?=T13!8-*pUc+bATA4c<;#CctbJ9Ne(7Nm|HHVcPg%3V#y$}O|D+Vs9 z^e-C^J$f%@jGhbJI>WAE@nPA0xA4gRg@^LEzcO&)5qT^-WZNQN(cXdVKPji3^0s_3 z<@8&_7i~rJw2`S;N*lr7If7V89nm6xircDO`yv5m_ct6ZORV35Lv$zYyiRJ`0kiEK zjY~?<82J8{=3<`jn@HP!qBy=!V;^>{yByp>x=nm4cGKTjTQ`jF^9qjV^!Ppl+hm_N z@5gs&uE;K|-7autiQTSsV}EQPvI)^De$sYf?+ie@o zGyd2$ueHTRlr83cDc#xRY3+>ipaY8!c+w~JZ5{B{^a)&bTCl}K(9s8-wjBMu8rwYn z8avpmdrv6dr?j$-Y_${L>?0VUF-NL7Cw1M0Qq2V_g0Ptw~9jT713n4!#<=f$xvW zW`;O%fnRu^qQ2@`7`SX-z;fWYoBCzwQn~n*&)Ggho;YI5J+17|T2PW08!frm5HX@{GdrX6a^PU~Z?6Jp;O zPXqF{{uFo&@24hnt2#01+407WBT?+@9R@wf)vg(kz4(Y@iQ;@5n-uFce~=t3pNv-; zQ<6OG!+2`6-@C}!)Z@?_!-d!8!#rnks-B12{=l#OE57?QR()$vYc%QSklVA!@G$Z` z1nqtBVfVPM%=g$mP-yI?U-@N#>zx+f$#Y{4cdMVDX~< zC*jj&;Nm+cU)S1GG*Wg8_XMO9t+B&9;U}Pz1Ll8TCkKq5>!x&)z`ahmXW`G5YITyp z|5%-9{F0rdb@D6C>;DIJa`GRc6TOFyeb(rulf63myLh-Fq+e>XXY|ivV_|HLW5xt{ zULm{1M%6AoC;T7qy+oh?oqD$OKI~h*r}f9NeD9~spTuh~zIOXx3^?B`OOOCewoMwBMUg^w%?A7(^ zYQ8HP8{wGZUaWIV+GotYVJX-t8UhH_{*1uI7o?SC8Byg>}ExjRDVVet> zyZCXY8ms893=Z{a%ZI>+?AWiX&m}J&4D&J@*MMsbB*u>|E2cxg<(Kim`YPkrSe0ok zKpwUXpHTeD2#3ZGp1@l)MyStEY7AmdZ1K_Vb6W55v@+88DqNn=CGr9KwPny@+j!n~ zu>K0X*fGfSAsXx$B--KgY3`g2+(!=Up)VZsoGxtxCFTcZVwdn*UbU8+OrG)9<|P;S zl>W@$6y9pjn~*`0UrIT$>01i_{4GZwI`g*tT|?P5)a7pB-S~^-8SlfWj^@#Q8ei#8 zG+d1x&+>i~*Nx|2VO=2@d((D3ZO3cmZ1=h7Gp92a$5V^Wsq9#vE_Xb(u;=9FIM6A- zxAAN_^t>(Kvj|-(-dH-(o899FJ_R}l=$L+G509hA>Kx%~c7Olq=FcBfFZISF;!(24 za*xu;s%|{PS7cw($z9N|w2fzi)28*oLF-vlxjE{CgU&rN=u zs$V^5I=7Hc)pTcv`Sp0#y>QniX$tx|ppC-GcUJ0AWH^$1| z6Ti6?JorK4H=B(;qqH$&Gcxrt!@fg?+>|fh?)u<2Y?etbG3-;a!*O|slYVwH$BEC3 zZQq{vr?9sH^DEEq4xK4`{%lO~yd^P3yus5Kx#xC+`15w8eOjes8%g`L{4me@i}O(% zD<`o(8Ehm6f0>s@MCaT{*r54Q1gWy*?eAe9z0wSB}+(Eemw5c5TjnrF5+|Jq;$^LFK&MPK}@5>m6YWx*_3w{^(Y;D|t7VWXb zKCW`VziaUwXMZ=DGt{&r4m8@|{kisUJ>T~z4NY1{pCZ{nx5giRo5+q0*jyREmM^`? z992)NU&TSyg?`zj#(VXzZxbng+B)F0age@j9pI|-0(0U+!1`!w%fWMyJFvs(Q|r&; z?~&HtL+WecuKYM-=X9+hzLIxxKebrKelt4z&~v0273)Lv=q%U5{62Mtxo&(9SNF|r zdAGfXT%nH_w<@-AulBSTtPkq0?EynM{S2tv&E82UZ{kh{>AfcGk&i2gPVVcwJ4U)A zk@sgkec;E3xf9&9=hHu}H#Z9Vx!!MDk@tB0p%?Tf-o7OpC!NTJI<~(s&QMqN{QPro zSZmvFP>8OG=XUt`$Y^BEH`^?Z-cyT%v{&N$(|UovDhAkL=01-Q&+Hx$-!>BsA1Br? zLC3bOKTqJiEsCX0V>Cb4S;={!9(_c1+#=oB_L01Z$qztptz26RjA4bIg|-ZtbJlPs zf3~gF6L({#50t&?AM$NGNvj?EWjg|o*fDp1un%ynU*>px1F{JnHSc*7I+8D0+lL3G zTSpRGh<&zScxjI0C0tHMy8qd1W%L@xtLhvp*t!3vS2UF-!34*NINc-X;d@2I4{r}>bXduXz;_Y_MH z<>&50Hi{h?-VtCOPAqEUy-MzQ>1>TStiAKnyn=L55gfVBE&a&M)qXGEIUig85%~q| z>vA6|KehLC>I{<|baVEjwFv34x}&H2DP`(u{IF{hVmE%@qO}x$V$a-?GVI@$EpqYS zp0mkT*dLy(_lsy_eFeOBE(gv9#3G%YX>MxE=-aL{ec20|6VksegLb9mV<$5ojK>|N zTTkO|?o0j4G^W($G|Z*hRr=pTszcSyN>%faxMe2=lIe4 zMm2LSVipF>wt4;-Q1M^r}schCEIF6GwdbcZ66yXs0q3ZZ9a$TpE#Ljz71_p;G~OM51kRD*jMp#8S^|0E zw=L;DZfVemwIjxjvc?SJHum{q7qQ4-bBbeWd+Dak&Wou#5#6>(hotXCKfjh=6X&m> zudes9pOn&(lY5X`b+0eoM?tX={~8T`eh+1=(sRIRWhk6pztug#2Kz$H*`9TCz`gMq zMW^m@Fs~A?+_p;gAs!z^niwM=y+Uc`ic0@ndkV-^_gmx(_TG&0@G4nr-W<v@MbEi5B@I zZO7Ue>uh9+e6GBt03QYU)B~j}3u?1NcM8kMM``9_6?m_h;ihO9FRTub3-g}w$GvmM zUV^>F;yyHiv(ndE zaCoCRwVgLlF4Wx9G5SXKZ0`Ha&fga9R*z7OU(TG%!rVPd-fFY&`;AlCN?)dT6S&{l zB-r(4?rDLp6QLox=L!wjtg-&5yNI@Ld{OTv+P+8&e`z`PiahR=JXgB(`<1@lrMD@K zzcU7Mo}KbZKi61-Jt+RzI>2tub;zjsk9CEZeJ1jE}VZ%?!t}NW+ z(Hc**TfZK!c$ZmS53j$I%sQeW;@Sx|4ML5JSA zcS^>BA30*jJnbv&G`$v9*@SLi$9HS0vB%k$t>a~o2TPCqBXI-TKQU9 z)UJ_yg-h?kmymyEOZPB~x7*Py`Be4b-X*>ZdevX2w{_88K&J8g#TG}&o!y7JIqHm) zPtX^?H>Mco-n|?_O!Dt!YMt4-#;n(Vt?JwP2y_|wH0> zDqA<2JDaK2zjm4^Zg;x(xpta})_u+otM3EC>rH3l*XYmhlkAM&*Vo>p?aR1#_%rV* zbl@|m%NO;olF|Dpq-~ueRe#@?w5#qxl(>^D`}nmD{ZjXr`hARv|ytKyO6v!9>=iB)F0$^BwJR!uY%jw z>8*}?W_*yT({&a-Wd7yOgX4*v7f4={S*zktpY!x~(a#H{6=#Bv**O$>huh!x@UhtM zYv3;qu6DLwq_2X`O})Q9?#4mt*NlVct9CC=`#%;>?N~LSp5I?e;C`>l(xdk`)o-4- zsdajF@2J`C?|tFf{`>9@9)6(?tssa{h9P$8*^IC z5w7#~?EAGE^Mij83;lVu^S!tX-&0H#T?5+jZm;eu205RLt=V;ho0qnPvGX$4q!#Y= zXWyeI?dR4@G_SVfjK^hTI564fdxeQUBqxniqS?Nu z?D;__+7Ic;YF}?wLGLicckL7VfG)mSU26-1N$=&^1?Gl~f61Pp)9WSZp#(iPCPAa( zzSa@==jub_GMU7?f z>|z0NH{`=O8peWnY?FBMs`?eF|IuHLgD2`BVMF4eD#%2%VOH`J&}Fl1KGEs-Lrq z_wH%MKN0WjDPU`sKaHhI2mXxDzwfZys|bCh@k>s``9ufg{TSu+q z&+t9PUm?em_Br|1jcB)iPTJD`FK!=QbO!u(zUO6hNNv%T#f6N1q`dZF5AoCJ!wUyB z=YJ!x{ua}tHB%n_XL9_eLQ6FELDSP}=hjRE%lR#ve^ojD5#-w|*}FByTxfRw;C-be z*+;(8sl4y63|+*+%A_0kt(XAgx0AFN>R^8SfL`R$qwieb#Mq&4hxHFJ+P~Jfgo@(t zS$>D&FyHlIZ(t91w<^Rs*=)}uua{ogo^+O*UR0du9btA2Lk6WG;8o0{ywpT&)*4Rr zO0M4LsHeCWbk=vJ#+ZC%p~<{}JP+n+cM|eAy7@A`CzN&H3u_vLuE#uf^oizTX^5$W zwpQQwd40?GqIq{Gcw{fvX54yM-;;s1stu4fnoF@(7F=rp&6njTqSy4EQ~c*c=p~vn zPPg}hvc>FJr~9v~9Qke=>he!0KRH{3=RKL;I}a*;yc=F5k1m~`N*9#1`?9sZY^&}J z&`$IuV}#wOfi}TM+C8pn8S_&4vMuXOl`%)NW%LCXwK$pg*%%;M`8l9y%V2ML!5Yl( zVwyyw_Rekn7@u#ey?^9agRiyV?mUM+GOSI~bTpca%HR0Sk%PL2p|$i1{pxKB=t&b!ZFVC>P`BI;o_u8^spDX~ zLLKC2yiFP8Zt-~7j;Ibc=jiON?#m?gJ?!>SpM15xi(3{iww1t290m=*{M7jo%u7R5lBJ*mVr)lG=D5xLZEpapv53LSNV+>wER%#~6#xw%2+lDi3(h z)jW_g^&85K)<{#?Q`7ypdS!esD#zYrRhRDWPk)nsOS6eN6!SYbhpOreSy+87VqV)+ zIB0c*uJj!d?oZ5x=Jj3hq?o3?#S-=24!_h>+;YB*+|u&D&&eNOUW6~Z-@nAClet`I zTQiR%W@QTJYYoPp)%nF0!XX_Yf9QD8)6vle9oo}9RrEXpPt5T(|5Uq7ZkGMs#ZS;y zv~QJfO1IQkoj&Nd_4rP(eEy^AL-Ts4vlq*{Hv&%Go6F>gd+1qx@6&H#0{d4>lbfpr z9uC!sW!I?QfLP}CF}CqtS4%_ZEYIt#q8Y{>C3DbieB0UaaY0g6Imzu`Y96C;^!wzi zXV(tr!=YWF+U^Cl1E=1Dwm3`Gvcmx<_^>6rFZ52?uI&T+*BH(3WoR#;0B+$CtlF>J zzuDI8=D)~-@r`?S@SUW6+}V#Ud3$knCYYXsX;{w3r0(Wqys>x+ z*HvNIX!T4WvU%zauy@JBO9BN2h8}fH5b}=nCcQE zo+|zAUmaYaZwpgihPE0Ttb^O)jcm-XRjuvx5#toA;{cD{Q@NFeNWT>M(UyBzUp-ga_j^rA~rxV#T zwQak{*|lpLPrQe!_Zp9G&hXo`Mtee4e@6${)84w_DVlTo&9MJ^E*VLa{mv8}`J zPV-Ff*XKOFea=2=Z0xJlKf1Y{@29)(`%?FaK^_J^-2QHLZ(Olk{Dl4C#NLUivNhW_ z(!)RY!$12mA*MUr%}Lxdb>z||z5BSNczeP$AlrI0d@w~r_>Skv#CP$c{?w1|ab4r} zCEXM}4F&E#ggI+yo93evJ}&ib_lbVVFw%9Jr!>BwipCTz7n~q1ii4TXId-4yw~X;l z-qmlD2Avr6sX0o>(;xY~-r@7>pya*PN}2g7@2yr2arSnY^Rs6;e|t`QResL8Lo(yu zFLO0_CxmAxdx0{|bHV>&fpjl&)ZH%GxBY&mFYBeu{=AMdUeT(w; zF4aQaVOx^dxnxUDcRH@D_V*OBe-^tu%$=u0+?CLIt$h~^TJ=6cxQ|7<7Qywj?L8f` zLG>s32*-BzkNPMpbC&fFx^Hm|S~!0n)n@OQh+apx@CY8hPk_7+Xx?Iow?W4_zNfFd zrAZlaHJFsRdA%=*Z7F>j zv}rDF^XuH%k9683E^pNE717vk@;SZs6 zI%~V_8GidGQ_L#Anz{P*(6N&5Wv|R?Z<}w6WcaoS>lfZ(&*tV9*&{p1?}Y6+L-nCw z<8g_)s%Py@>puFI?we(E;!$<9PE=ZF)$>SW-zI0SW?d&gcJ_h2i5EVIt+QEzfS7O{^b|C&+_A_Z+m|nc@RqumcdnO8nWl}asN4o<6DgJiJX9Gp3=(P#T?jr zx?-M{+hXikb`a^{LF86K2a11R`wjbfy=Ii+pIv_u=VmFUU>_>~vdZ6VM%|v}`$fp; zSi0K^KYslIxDN`NE=dJABSRNVI(UYBn zwKAsHnPb=((YM^j?wUEqr{MSK=F1=Z_3C`3e|wWVcUja-oEb@+;oOBac$qae`z5mL zYOJ~4#~S39DaNq1_gHJ>E=#MJijmTF*^Li#b=HvY_j@DSD>;+zaIjZGys~wO zS5N8O+qOr(MmmV%*Uzo23xYq5uI-+U5gzaswC6ITP8of4vu98SpT^h)+^^`;cZImi zVfTyRQS{A%AMwAHcQ(ZneH4jnju-A=ggmjY68f~fguLfP-+CM_{()md#WFa`#ED7V zSL>s!jeYOFmVBk1-~JFDc%GZ!r2oA3l>$!olH}`2p8cb|^5{yk5nl20N%lUh%*YFF z@3E5>KUQY5yj_lxFxtL%z!f zmq3@>J4?Qok1twXU{~nE`lkFyx=!prKz^JgUI`}YWoX++0lLydB0BG z%A7)b3Bskg$+xg=I%yZyq@}Nn=G@1Y?S99D9c`}$N6zK<-SB>X1DyPsK7YmC9bLo7 zPj&f>+MeUm?O%SdW8dkN&Gcno-QJPOlE1*`*Y4=(8cu#r)b=8m?)uRK9c|(@@A6xp z+s5xplfT5*4YIh@?ASe?`d9gUki|0JUUa_MrOWUAct`sg)P1|lcm42V z9b46Rr_1k~__29WSWuern-FBV&u_G{v4OjeB;iJ_95hF`uf36yhjhnF85_W#($)nt9)6IhqueEi?(%?@7$nQtIF z@xIRG6a9Gm?ArLjj;>LZZ}Ro;f>+hw;_|^hyFUC8XM^u^@ZGY(lc{&7$BWH$jV6D$ z%O^Iw)7K9+>1}jhu-W^4{r?UdC3a@J2id)-jN&L~4X-!7^XE19&cA4A@BI10d*@#~ zvUmQ1vAy$8H--7*n+x+N3@yx`IJ_|bjFE-;tz!%G=bc8~3DnI|_e|=$erB(qo6%S1Ej|_(GH$*W!`4+{?F)R2yt4{R+J3(ZYnML`&sK5S zSYFMy%SXpn^KIxOb6PcT%*bMP=Mar4do`x;ov;6YxyZre6YUWYf+#KLQCeIvzI=`l^<^Uz;dr4WREBl!*Gn73X+7(>c zv%XBYHD|EvGoImYUcy)Ej5Uc%iqp^+&nN7B2AYiKGoGKC`Hae<`Ml`g>-RD;%(WBx zyB&>h_B6I@F3Y)I=KaJW#Wmk|*N;Bdv0Gy(`F3QJ@Yw6xUz%#aHS6acf%YGK+>OcP z&x_laT>Ck`eVgxp0Ph;3Xgi6vlQMR0b|7QsS-rd_)%USzA5Hs(p?%)9f3(o)R{i!4AsoMKbs29$SX*Rq(rm^5@RU#qH=q@16Y#_;e>*@GO2_ z9#`nm8N9yJ%|6eL4DSTAu?EZe-yqBbYhs*wW%XYne&l?J^~@{ky<&Adt@}$RCUJsTRIcp zrnlxym3Qz``P}4i#=6(cbm`J$&KUX4BCBV83!*Tu_y@jUshCm5_f`HMFT1r(=tDL= zpk430;?I4%t*kx!XvbRQKqhyQ9Rt1uKf<|B@kF#jTf4>&aOs_`DsCrZm8(qat43vl zXW&WwnEXY>9nfiE@MdAX7ZpF?Vdkm^{O0BtzvAg`V;@O;>1@-+D%Nr~R*^3$9k0DY zzIN?(Ak#UY_W8=eq%hOs2 zZGGK);B%N_Wo%bdh>0n?nr`jt4V*LiyBf!_D}!x4_dUiRt&tZY8|3qM0*~}RTHi#D;d_v2 z8DH1^<^}9q=&Zx$eLL;Z=a8rDiuN7aM~Un2X;43man-Y!>iePW?bz>JchXkr)4=%; z$oo*(d%zBnwVOL(f7pt>7p*&eU#DKY|D^t{p7B@J)BA8G_Lbxx;Y=^Y>aZW6cil7& z*mj*~a@Uo;7=9y;HZNiu4xfi-fZKZbknrcsg71oEqjw$Mo;C6OEL*0%PRUT;6{ItlJ~rYk=4I6@JEdN}P_oZ0?ZHz(;&{qSQ2I zn!YvSzC#hdo8``G>h<^0M*aCds^^T=Gu(Q3?x=$&*xjQ+#~x?UTcUg5f+=QhfOf?% z>9RWZJ3d0c$SV3BSZC*w=dYWF?vbsAdIos~xh}6qA9j0T9T}><<_6lQtgB;vJA%1| zbd0?J$Jb1?-xO)A%d_k7Xk~%zNEY>UX?29{S>Mq4EP9YlCB6}S&f^HWD}^`!Jm{_& zS*CTSI}eJ#+EYUpFLt~0j>Ly`#&S6@#fX+VF``Vn1*{V~_1@8O;=pwFzb(H$K4gg* zZRoO%{3L9~rOzzto^>C1CvD2T)aV_+aF-#_FZ!T=Knx4??RIBp311oZpdBq|T06>y zxaQ+rc2k>+bDq{}{j==cVHkZKkKa%7ji`jqXe_E5@8WxMQM|KyXZ(uC80lW|(8fh# zXNX<>O*(HG!5vNBA*wxZsm|$cb#pqcpQP`C!)4Q5N`vp6lB3?aF&Ept-pY3wWBeJ3 z+Jt(HS7V{0@((KKY?t{vX9hY~aOW>;{|z{23#G}fuae%CrA&9p?How&A!)tl@0Q~O z)%|CcMeDUPICRIHSp5_@du)51r|K+$Jbg(2?kr(4aX!gnDJnv+Vf`I7*zq zE?~^sM@%eD@#in_sJ@?pPxVojoS?_j4$rcMI39{ey<@1ehHc2e%1qyT0ruCD6>Cqy z_IY`0E+JUJXVbAP>a1Pw22Yv!fzdsbBa17CZXGx$`nvkEXA@U)f7YE%ME6^E8t#-J zV=Fg(Z#~D{zo~PY=7W2)-4_=#c{}%~-pt|+;Nm`w{3~gvv&5PFat4`f6Usvj+=z5ocos@k8Ic z=NCFwdpVcDcPVKjU%ezbqa1DTQ+gbId}nBq@Q{a28~=spGI+x7_6W{*bzXRk_cf7+ zJwFU`t#|kQ^OA*)9RuH^>0dPT>Co2dWJo6L6A&A!{vtW#tPjmhd?>N6b#s#W^SQ|I z*~!Rzpg$bQ7L1KUrKUl97DH3(%R|Dw(VZs3VSn2O9P2e-MBYi?QJpdLWA9zwS|C-4#B3jFsCX&^#f9Ps}=A z_M`DL)2lRk|m6F>@`}&xM2jyAaJy36*{2uGG;P_kKzgGQJ+^W&j5B`tS({TM(D{b`7 zb}z8_J?c(=qjiJ$6W^C(`+TRgp!Zm9-V`5H{qRo3Nb}-P^jmSP4p|0=W(LPD{vFtm^YL_ixTf|z~)?Q2Vp!GlFj&&bx7Isgv zd}go_;Xc#j&gokr_?6>pQt`jkPVrUWbKx$N89sJ&Yc9XOOZg`J_2ta^;llYywpeZ| z^w{+Z>l3?|>7V+}3Gr!1z?qC88rv*=&3o>opT0%JnPcE6KQgpP40x$;#&POB;p(w3 zP*L2=sc(%b&`azX#n@)=hbV4`xsBdAQohlATz2|^bS9fd_Q>SLwim1K5vlwk%GIB_ zw%gvRzlZst_|Y9IV;BSXPAq26cVp%y)YUuX@@p%*A&q61hyDp(Kf8&a!#i>P zCC~R$ek^r-(;skjP48GH^}mvzCL_t708 zMjYMz8SaaiV*jGEo|fNgom|%YWxsB&9Qs{zvyfR~ ze(4FVM|d`!jc@Z@bT;4Y;ko^6zVWQ`v-vi*%BK_u+wv*JAv^<}HhzDi*~V|_Aamd( zYxgF9cJV9FX=6j-tm0=vn)!oa94fOmCswU~IK)Qk{1bKT?-MTKTNzWZ13h*BYWtKT z{8UPV3ZqLaO;2eA&+B=@Z<+bf#hhs_;7s!j=xXH*lQ_w^+{9ceW4|4cDHINF1b6>K zW9B+~s`J)lJSkMilRtCo?3x&^eLUN?+qSj#*$(U*dqXw&TZlOpzaKZsk7SCgp;@tED`P)*?JA|E`A;LW7xa5}HFclya<=17 z@}4Cz+40FdWt6Q?KEpbB#L@oyCf4C`3H0Ay4^DrdnQpLCqfSRNL6E zST0%T6cbIRn7La%IAqUi=(6`yxsxV7^nP|scOUoD%HZ0kdaUzonU}BTRv{0~HDzXM z{)6Cqu?)VY(5rQY(fsiF#mE{tLE~ZQ2s#raOwj>PKM1Y5-BPA6^KvIgq= z#3cj9;txO@KE+u4P5;{t)qS$Gy*EX8UkWK|oWCdf-RnV1?KzRfHS`#`{O?~M50~EM6Rrj{oe(|R&tiYyev6eo ze|>|sO}~SUEOI=dzS+|$-bR= z64~)V4rB7nD@=!U68Z_YDSNZ>linDMnRDqI*FD4pEAN%en}hu&Z5YedM)H)uTG`T8 z=`Lu4p3$?Nj4Ng)-?O4@KC%2_V)=sJeHTq6o}WQHZ|&WBk#MwWJ@}IE|4heYDd-Cx zGMidQt6t_w{~nn1D!4J{2e_quYMr*GHL0UHit5|8o6bmZ3f8PU_v&M)duDa-UUhlT zf_WErESsn4&bws&$H*hB2ZK#yh`rkex%XQ|`(+35b)Jmb_Pt2;eUD$Wg!37z6V=gu zFwuq#%$CejT?(*N^nt3%$&*ZM+Y@m7#z6 zsr60C4*h5zWc&4HrKa38%7;0a!*?RGz86``w&Qpj+je4i@+!05-wu*noM;j9dH?aLCY+r2^6W+jY9y0dg2ZN`5nQwZ%A@U_-CR)Bq zI?I+_9wP=Dz5l~KY`yPa;BJ@FY7=M}GL$~a2mcE+G($sqI%VuVXgtunJ!QtSF2=IV zmbM4lUhaIRZOfoBW=rds9Pd!DkH&A746*cn(0&`u`-?fl(%KF0?dqStK8QT*S!qN^ zIt~On4DYN6_u(0$W$?7+yu)9$Q}?}FJ1*7Vi?jZ-J32Q(2Yg3nPI9)Y^~VC{g4W+^ z&rNn3*0V-)Y~a*c?axgTc?Y0A~~ykKWl4 z%-W;M@JZXomunA7>r-`oj2RL5BNT@{@Zf5_`1=ntM;Tnjeu z?IzZPNtJ2LircqsnlM`9R)ERAY?}2B>7M6u(9gRs?7>*y%^`{s}^HGYuR zcV5Y_?@sxu{jFrm-P~{D`PMJcooz!ur^x0!u6&Rae8N|c`dWbP7rZ{|;kY=^N}E8N zJHrpZbJgJY|L~j4Eu77;kMsTi?d%}X(FG3%_*qrjEWL^o#7URNU+uds&|aQGdbo#k z?x^=#zH_FxGs^9|Ej-(p2gq*aTauj#e0A#kGTf&Ge%=AmLiyXXbM<_y#yOrpJP(`Z zS@r)0d=dAbB?boG{R8zmL-P=jg6z6uUb9ct++>8 zaZWx5KPs=}x~WHO4E0)qT)O?bhO*_@LGD1|blVObw%y-2ncp`+Hr4xB8V~fF&Oshs zk_YF!CC*``gP`kaq6b*JbR>C@rVpoYrJ=#=Xq9IelHtWnph{ECj_2>S)O?&Jgc7k zW`0;!`_zQ8&j&I9U7aU(D=ICiY-)?nj<>&LW z#WU&F&u1!n&LS_{(VVF|PBoAr_K}w1GvBT+!^yH6=-&EA%J8YlK2Ct6t_)=Z1ur+r z@O=Yi_-`}QvTGb0f*(92el+Icf3Y7hPqFcMci>0mBQq}My@L4n;lLN|2gE=56~FVK zzB6MT?!OZcag~gpPnChkSz_2O_f&ZfU-A;cAnK3Q%ifGd1M2cPxmzM z3$_C;?zdUoe1lpx@zMIGzul`lZDCLA7I*)S@qV;?S?ea9#h1ZRYGti(C-ZRLPrxsBurA=t&ds&D z+#F%F{pK8eX-w0;T)->+EMUyiZ-WGw)^)-=iFcv&)Os$)FWi}C1MTrSu=nL?o}W9# z#lgViZvF1I;@-%1_YDeSweA(}7Vm;h}}sw)t7A4SCJiZ5z$oML*+ZJM&r1txCil*g}t^h#Df-8Os@A^nBkl8uwom@NdQKc-d3t}XB%G0t zc9G9v^TKa!|9Ja>v2P0x{WoeG_Vux+7Ymv{ME$a_qP(>=yJq*e@J-9hFl=0YTH*{k z?Hj(ogN^ns3xP-W7aG#0xaDJ0N$V*35}lSV;AW=XJ7Hg+c~#Es2jnz2RThC{b=t{W9fi?Zt~^hyib@3*fI4MV8@2wBxGiAbleHBH!e`qHOtu=bDTiKaSRIv%gt?z6ou1zF7`q>=bBQfQ;nFN*`y= zK7&0ByZ)f=fcd7zYvgDY+bK7Np~b!-tV@CKu=G&A<)pc`AI(K)GPmZbHdgkecR%Is zM#^q7(5SkZ8?64U{J$j~F*dfEX8S!SrLBMDI18n2ZVh#L+V$2tmu`J+dVX8o`-;!B zI{p5a)jxOL%kpo(k8XLAZ3Lcx|Cx9KW_db6z5W?`4L%Wcn~rY<$#00aL+Hqa{7kn_ zDM$t?C$ByAoc+FShWjGn+it0I0cefqhy~rfMkj&KOvrouy?&h}y>u!~-6qOxn>7nH zSGn5mC37xs$9{fme2ip4y-ZwB^Aqk#=9v3{Yp#`5&^7WpJFRQAm#%qQA5oroqr4sO zc*bXQzh)g*m8IW@U!-pWkLCW%prX!Yjoty|J^(SUQhvzr4aTxTkb08cSZ+R<7Vw+~&725B`&IlK=E{Y+Lh2>l1ueOExV0zJ0eF zqn!SHztW$5XDZ;+omX_pJ7jz0e`;_03-!}_jPUnHw)p99q0c^Tih8RWllSiAnvK2E%L`UUjHei6o% zunx)SI|Lf*u)9~XUgFz{E=J0J>#Udla1J`7EUc4)|HGrT0ju}m^BRkCW__3PjK?ao zu?RWZ`TpImO`GC5>w@4917ZO-685rU89d%V2D9V?(Aai78L&o#Ci@;lA7h`!xg4}C z2bSw~`)$f=49IEC=IhI@k6WKL-G`1P%YbW0^?Ssk*^c)-ccd`rN~BCJQ(G?0P4 zM+~0!YEMXFQlRU+GEj9z1EHR2G3gu&>4+S(fp*~9;cU$O(UC$ z$C1tX17uR`%RzTX|Ml2Dy!DTy??2Btwhs>*K;QBA%npjyI(uf*DYQ-1HI^uP$zV1|3J1McorSxwf0<6V(xFB@+Ib_ zc5lC48EazMzUt>#qjP77du_DA*6PBM%k4ofs#hmATNz0<#1`#;HI$9od;h&dvLPSh zd8743&HR#Y<7s{wVqn95@M+u63C=IS%)P~dzFo&Qqy7DJ8rY>h(~#Zhdp#NlN!PVY z8CbkKm;HrDJ^%s^-%Kh2aYX;b49X|cq zGQ^7)pRDhLQ@s!FdnG;y-DP}FJpE*%j~DfL0@nII=SQuVHrY7ch{yI6k4L6yei!>) zG&V%{@oH>qPJ^{eI45L1Fh%`H){^B3+SZi1wiVG6ja4RIOF_?8jaB%n>?GW&(wt6x zXC#{wq+xJf8dB>W@r4bsE~>8Qp<%aTC1vK)MfQ8KVf{W+y7%k6QfNaRJ4a@29KJWB zeO#`!>9;y7#G4(4{f|EVJ_Bu^kzIM*_B$)gneF=>{#!L_o7XyM%P=c%(Gcz*DNURk zWo6;*<3#odp3q%k#))d&sqXb^U!#w60^yV_x&!Z;J0Ocr@`hN?J8^bDPxA)F%mvzW z_VbEl-w@x0-UmHChMj=esN-V0`5Sn;eptH9n>W1VKvsr=2} zCy{+}kMjTikiD3h=w~+L3i2+^w!AG-AEHej3!}#Qy+6-dYoB%YoS96pm*4BvKhEpywf4F^ zxAi>Fde&txh;U4{r#U`myO)&R{z?7=oQlE1v&}o*`MjC+xYhVJ_miK6CQa6YboaRI zQ!VH=J!5ZWTP;6Yn-)I7R}*ig+dh8M_`Z%G58cN9o>vs@;RMF#S^u@;^BK{` z%fo-y_e)3-^&9@G-gZy4Uz8K2Uxl zI^^TPSo9add{lFFV94j2HtztwUiGax{dp^#t;e4W^UZusZ7TYw75VaQm@ZbGfMaMw zx)?k)wiCwoZ%S5GPWoVKq>sq6ajthkANlvDkCZQc)Eem{)gjN?3;M{v5BkW)hRVr5 zQ5(QhnlXUy3+#*$!FHO)uikl>I^?7B&dL#U75(g+po}AxZwuvVOTKF-y5Vx+%d`1L z;2HK?7Ux|>Kl;<38GlTt!8bd?G5yG#BOB9SJoQD4>GFT=nEsJy^WQwCpZcd4F{VX_ z|Hd)>2r!Ns(_8E3RK^^afnRM8a}JlO!CzZ}AD zbEg6Osn8{z8JR^8t7kzbfpFtIfx^ybgbu|MnaFf5rXR_cs0F zuvNx@&F&Fs1{-_Un!#rCwgP;y{Yb8Je%`x+z9$`U4K^dJ32MJ@?7x`Lie59D!I@0& zv#z!^O66Bv+j%+ZaQ3okZ@c^u`JhVcdDi_r^Zzy0p*Q>~cgevc{L}8NllSl{VIN83 zzR$>K@wOFyJvR{BYGCT}=`Yc)`J4s29s7rY*Wl=U(;LpteeQA`%M;%xXult0zh`kT zf0z%%d=TtOuuHPRz@FQ2;P7@Sx8q~QFsuz=OQ|Ct1sd9Ye1f-lE@rGYj9-*5@O9?# zj&C+D>^lIIF@Hwmf#-?%Gk>c6uWB1P8x`ARypB6Bs>_(WBSh^)^OFC{d6C5*=B3vf76s9c2EB>M%yaJ`D3$c<<$c zIfz?d8;hsq@Lf|cTe~$b3S+_Gq7U`?`hb)Aq+BOHQts1|7yGoyu6mh>a z9oENO%6!8Fx>OZ|`8oFu>kNzJ5I9XPW2`A4JlVA1q!b+28$`PGPf3b)^9Z^dHKS8YsVol>Zwkb=EamrrX|Pe-5%zTHxZja9vA5SGTz(zlG;Qk*VHEY zlaHlMwM{*>O|0eu>*LsZAKVGB;Ja!+Dj)jUN!xnlcRt9%`$^e z^hwgT>?+=GDp#j~V|JC%^#Y|;7r(Q54)_d_FB#R@#3xzX^W|xq^1dE?sFxQnSSznl zPj$XXo%aA!g>|}YT~e(ur_3&-UuekPRJKNxUFiI!G2y&~@{}3%-rInR#)5n@&Qfa6 ziaoC_yK|MmGDO|Mb;#8v>a$$kq1a}jy<`S7xQu>$n`LCOM%j#dW(O~WF027DuDY!( zvTeQ~Wk@44)TMpmTgPRTZP9uW|E+G_$m~)MC*MKK;XK#FNm2<9pPr@nS7i{LCE#u6|{CS$Ev1_S6Gqy^7G?^Rzw&UM$SBUE{ z53C_`=>+%Zt(@)SI@GhZHU2x`neAU%Ogq{yuXp#uFSV_Gc)P$+&!Uk(SCisTr1Gy+ zR_B%CIq5>j=Z%bM*=f;&@_w>JO(164yf(yzVyNQoOj*>2gaa@V&@;lR5PXt<|?= zK)mXO?=8@b_d5+H@rC!Na)wC%^|^8L7;C@ukN7Kp?A&}V`JXc9*zg4}JLAkb)HnD) zm+5sjq+Yhe zxu$l}S>vz?FXV@1;x&8={<`>WyjkeR;`apB`|!WFm9g74)gKYr!hqPt{pp(SSbGXX5ANX7_B2>#mIQ?Fn`dIp-6y z)KmVk$;=M)cSmX9MAbJK3*~z#&plJGvv~?-CIa6a;F|_~lAS63f3#eBnc72qI?SVw zLGRFqa2P{xuRc$-4s#VJ3nmwS9clGByw~|8qlednz0gBvioo5;iloxqK)a2L-Q@96LmI={z5(Gj>DAMR+FyN+)nl7C(*ybBsnFo6TLW{ z{-syRxAa2p7MvAeM9-@qTU(CGTrPPUuV3h=Z+ltalpERO?~3y402-e;eKl4a?c(aw z)W7lr?a*mjB}?{BHjul9xc|lOFbivH@i%O=2fQ|wxo<1&DNYb#5cpn{FlO_NjmPQTpV4EARE6_LPV9K75}^ee@73Qa>Afn^Og^{Qf9U{-Da5FXw6A z>^m(VRDZT-JNiz`*82B{4#&H}R{EHV3;?UX8D;c>CW6Js#8z8uLb$*`qumVTi@D8X zJ8xP2!JRwb+H%9(yUykwA>AQVhQ5s1HpS>OJOJ##qvt!_%cNRN&ckHki&-X2L$r321y1LxuNQ%4?;JVmsRd__+$JKGw^E#Ikf z=s&OPh7X1F_XAEIOTk8xHXZq?u3^UQPU%VMI?+!5Va^rel==7uet7?!m(A5-Z9lBr zixI2R?l@~X?W^)-2Esa-(*oftMoQ;s9wRX^xIt9=5LUmMB4Hh%f#Sz z4$foDFqc(c1$s`W{W$MFcr>|?_hItl@0tXWpO)hF1^I5?$4=f0K7O3uqJFS@%F`IO=l=S8TZ7bl^78i|s=qG_58j*a z_aRq*B7RPdy_946HPPLSMyo&@(F#0z18sm~wEQ%u#X!JaZA|m?4QL?RkJ?*#kEg>u z{(Ybkyh#5O?@8>31{0nm0CSEvaE->>#fhF@livG7VkaY8+x|(m0Q+4(%XO8zXFkYa z>DqMd9lG;v3AzkfTmB5sd%#_CCpw|?%5P8B>VL)`rE7I}e))9wzx@ySFB22^J@-FD zY4$nwuXX?1|A2q?Ob(po{%0u7{_V;!?tl9={GU#GviqN*^dj2(5&iMM{jH==<^40H z`JbWmDYX9-Y5uowCS78V^A*zk&rtd#;>`a*n*Z%rlRkm^{(px4xtn01%6tB|Uqu=n z(ElK5{%0sn47%J+n*Z%@Aw7-uJ4y3DLuq8Cf-Fb|2hK>=ZfZ-8KlPW~ylHkk z{Sp1Jot@Hy=+1fMTdbM<*rt}yq3bF0I>yk+aouUI?l$E9gt#vG#v3>1wz@?;kEUne8~eeG25fI zPmlUJ)Agfw=JSXCiQTJz!$Rm^k&8ff_9A%f9)v-MrDt{5>$7}C~#`ZLSw0_ig zsCN?2z#)BOH1jkp2i@vvI1tfPbpuc0I+6=Z?{jpoggT_nr(wRKu`(dr(CK)WQKp6S zUN!K^u%!#WExqHuFBzY2vo$SWro)$!ZB?E4zKSl^z68T}|DASLB4fVK%)@5)WNN(m zF%zFTJ-DP?J+Sg@TVK<KJ|6G! z{TLf4<&Sp*J?CKk}PDDT9UXlL5&wqA&D~0q|UHYSezofOF+vJP+ zWzdHi{*m%!lScTD+s@naZTE+^NNb;wtry8x2|z_B*)b%OPLv zwWpoMZv6Fo{L>HB%XEafG3|HgTcMP-xiM>g#!LRSb|*ex>FXOzl1Fq)4hv;!9+o>j zEH-~2-{gFW!)H9av!#?-a&~o!&LJ#4yPE1>{Xk36@f^n4R@QfJ(mE@Ac{6dcz?bX{ z$Ct6je9V{le%>#$zop`N67GXD`Skc{eNH}VpuN(fbLeMkGyd~le2vHG!4-~H;htT) zXQD|t$kq{ zWWIrYfM@M9$X1rC$3--)%x{$SX%ocN{2$M^d<o)zuH%s)8LVnp&#RoFPkWKd7&@Q zf3_~_TlIwlaMmt#{@%NSZWQb)ulM5n)1Efs!87hTwktNqzUT8jpZvN^TlPHp;>Ypj z>hap+@nh1!p*;#OO_A+OruX5`u4?z+4r!mpee*N;-ii9Ys5}gQ&t)ZN?DID{npgEA>}S%x3%gzWgBV)cDzZ&gy1!S{z+>L3d#6 zq+Tj}100i~U2P71&9*&)7B_5>ym@^feK*wF;`V`{cUA5}=5U5X)Sk(%9|Ob!&Bi5+ zH9Z^s!dOz>@5Sc>muOE> z3jD-}+QAbX-oSo@xLstH??KY{JVyu7XWMb~tu(YzdOGP*cnDu_pF|_Y{4?szf<8A$ zhlvXQ0Ei{|5!Y}EaEZGHDD57CG(^B0?f8YsSWts zXy4<>!V>%WNV|G%!bP;+8|(jXc8%ugWf4zRA9(*BKQyL|@$?w_J4Wvv4^5tw27mQ) zc^CuU&e++Wh^O;{&tYv$UhqC>GS>Q(bb;wUbd2c%uix(b`pDX1%qQ6J@(-L$1=|{Y zh2Xc3Wy25RSKU6<$=3&DM?UMunAwTF{#kZnlY2J1@LBKg?Dcx-D&Acc>1L%jMQQ5R zNUMx!Wp;vRYm>IxgPl+r(q<>F@p_+s<*AI>36Ra_Cd$JSP zHn9^bulM5Hm;AHz%M*`O1?Ccg-_azsbM8IWPpSU!wjugDo=} zwbe+QUV7TKE4!@u$!^vJcCaQ8zAX^GE1)*4{4=!oOx|91_O#`9XIp4z3wRQ%vGqsI z=adKT*^NoHQ?}{uqw;l@^QvJC`ndP6IC*JUccFZ~?s8*&-C*F9y1p7DU;H>*{w};I zGe!;0?*Zrcc=r`Y<6Ed>Y4J#Q=Lwg;b1rRBZjiL{)*}yQcb@n4efdG!sqwS-(nmiA zx1d9Sp_6*qYkrd)Kjz)@ z##c9|Bu?UN8oG}B44!>NXFi+3vd!<4zlQk)agZGWPUZm_?;8AM(lzLi4LM$q(t*&= zbl?c(M)GnwjrDB?%lEw4DgO%CB$LJqU>IE5HGFWJ;BVo3gxECN3BKns(gQ1;oRqZ> zi035iFV=SjpQL|$Gs&;uq|MQKI>6Y9&(i7I>K9zC=yv!n{w)k?+B3L3yjuIHQ74SE zgIhhFw`k9)?v0|2r_0qFf1rIpJp1~#FLEk&acaGfZN#B0UFc&kF{~PR<$c^-=tFJL z$D8S69`@Jx;o6uGu35m%wL0r!P4$Uds6@S|1=A=oJbtUtk9iO|C9(<8D z=+Oo)=n&Cdv{n7ut!G!C0_TJ9-emZ*&=#IqS;o)TL|f9Pj~Tzt2GZ6sw1upzt!D(g z(u@nGZGQk`G`D@k)zo&~_E8o&{D#g@-tKVdf-V}@O~)5wHq19AqhXA_k~z>QJ9wj$ zN$I{C&)Kye#;?@(g$7$$df4@Oq_*-z`u+T5?~7HOZ=Vnk7yPZHO+T^bW&R;FR9fqY zk5Xsk)}*Ta6|DIUXSY^H-ObsqH7(I|jQ3w1hGw^V9BIq?IzfDNcy_b@^^^3I`ncYY z4^aNIsVnz6>hV3AWZKzJu^B`^1_z8yf_!GJ-Omc1QBd3GRu|teuYw%h9op$1lwM9DU z!X9u_TVX9J`x12hQh`3QJ9kC&k$oZEP);z?Zniibo)Pe>oVyH}aItf}=MG4{H#?DV zMxvjyC!`G~Xkd3mJmL5{Oa2RWj@SIi`zpW3{nwRdvCpqi$Co*9mp?PHm~%Q`f#*+j zRfiS7^E~g7zVY}4-P0nteSNbZ*pmgiYx_xb1@*HhyIfq>ms!EPU}r)MOk?|O=xBZN zjgv2bn)-X3JukoC(fx>Gh9<957hDSu2PZLJ)%QK#zCoAMH8vjgcwZ!&!dV;2h-Q=Z zZV&Hx9#7dS)yep%(#dM;sSs;Kk4V;z4l=&o={AjB!-4ceFL?Z{-xnDZ_Bh-x z96Wz3x4sDeT02u0jL^;%fxpgSVaH3GcQXh4 zb!70(YFjd)xdH$ATmapo^3JY_mXjB}g7R_0`csnwD4(%Qw{xC#)S)%Vo^Nm#Q0ODS~opV0{p0pie z6x|xH57Nhj%#(G0%m95+uWYp$X^;Um0w7st?9!|VPP#XUSc$s+V4FkOmW z*+bny$~)MmEch4MampR<>0^3FcphJ_{uXe=eyiHuptTm>xA0#7t?%Gpah29q9~WYu zee3ZHQtG0|jrML0qJ3sEsms}}ZQv%@;6Zk4sM@Wh<09+S`axxM6#n;kGZ} z_Bmh;-=%A&GsdCMqiVyC*`BvH8MCU3t;=K94fLJ5;CROB+gr!r>5Yz7aZEOr$&%!8 zt1kmTjQ@<;QDttX%#Du!rn@{ownnCB=)-r17fe2_vYKyGuU^5=)=O`Owp+WZw~GG0 zzS(4$!dL2IKY9f? zvP)9-rSX5+1n9PD0(85|mC=|Q1>EBzl$Kz46|2tW)_;~5KzVDZA7`UtDXXiZ8v+M2qlM8o!zkl+R-!Jbv z`28~Z>CayKM0{^n%6ICS-|Q`MuNm~)$=>k2+RK@30k$o~a5tuI&5JQEKD)UC=llwA zy#%=aJos52w%3Q4Ea?RKC=uTn@4JuLwe)$dqsNts!+Tj#zX!=brL8iv*zb>#o2x^= z$ggPf(O_dqXLmav*?7#eXqVfZg1p;2@9xGiARTLCWeu=VH@92x)!>6)HrJz`t$(h8 zPuQ+M<_vGlrv=*IA-+*>toT%RVOssfKX_g~pMJdOcmBe7nfz?U6Pm-!rEcxQE`2Au zt%LplEz@@Yb=M8)UBoT+n(XrqSi7_J4ptlc>+A8^+&2`gl&yJKR{<+@bGE_r?#eI^ zCLQ2DEdSQg?>f)F*e~#W%;`j#(D&)0lh=W)8jiLv;H`PAV90&0^XhY0*%|5h-)iyj zQ+CvNqwYIu^fL?i1Pd{y*suA2^zI{0HsZaF1ISUn_v0hw>Ib884R#`e(dJf^Jus{8 z$7Y|Ref4`vW4{-uz1b13Xh-r9>)>DaenYeQ$7{GNyEy+ae?aiW`z>Y%SYEPVB>1P1 z-8vFq*K8rUiSEZlbBdl2q2Pw;EfY0IuWB-THB9;}6bsI;^{d!2Juj}tP%+R$q7UZA&E^V#gJ!P|% z{IBTJ*>2Xi(U~VPM<(5(Gu!G5c@^F4j5qH^yGvyIT^%dS`EILg=YorM!up=CyEgb9 zf@8qrB-!b4_Z9T}v6p%o$kz*j!^WU%Zzu7u@yb23OIXzKs^h ztLThhd_ndxKZed-p;+bx*#mrY3cXT$paRZK?rNkR>TYY%U5&nfqkn75&bI)E`P!?} zYVBk6waV)@^}%z@H>*P#tt}p|uhjR|;{9durhz8i;tz8b(c}T>>+#UDY|GAJ_Z_FA zoJyWVhg9Xk%j8x#Me#bd`-9!=HJGTZ_ta(mDe4)$p{?LJZLX6=iwlbPeRw}7;w+2psp*q_@a^`?u8XE;0@uTZ z_z&r^`ZT$CurjU;0!J=`2Ojn@8)UY&*vZ4+XiXiQk%!|l=oEMn=&&pFl>#%#Oe(YfN0uQw2MptmFW_*DE- zGDLeC_nNN^0&@n8M~k-LWITorIgdS_n||D!$9da_8T&uvOek>xTW>hU|IhXRj4#XA zS(kR!Beefk|0nwY6a2sS-zuL2D)z5EqDt$|ApPr3I{ho=ssHKz|8VCwp_iSh8@!6R zN78s7&4Q1Q+{xbkzUS9;v5&ugKf0YCIfk`?HfVK>(~r-2&KCq)X5^#B-@* zcO#8(PV~^0mU|CvB^{sVVEuh;0xnHX|p4od&TG7mKOeXH?x&# zbL#~?cm3J^fggSH`{jL4e!u6EZ`u4O`PphaGpll;-=SmB*Wes~hvd8JlktCO%h~q= ztH00bQFphDzSC2AY2T&LCDU`yOYM8p_I!2pOqt6lBR(A3vb*u_jCSZYC#mY5P^(AT z^Fw>Ikt9wIfpZgggv{*Vmz>l4COdcP`&60QVn^p48YdpE*U`=y@IAl-KkYnbtJWzf z)6$dnY5ve)lc8l<=Uj;!rTWH?+5!%%d$>At(Brz-;z;VurjEXa=j#X$`ps$aQg8_8 z!{WQjkh5XzUw$?$#}oP`fqw8Kz%3j^C+Vr(5sgyHCdgJtpp&m7Ss0H-UC>D1*O`mF zbf8<1Yy3|SH*GZITPnT@PwS(OV}^`LzU%kf`Ex>%Yz#uDaDUWh&Tlzca%Zh`y7=ExuktY&kd6ZeAzx<6Y;56r2T-RGWX0)ve)wOu-i`pNKs?w7i4 zO5T>G`|D|gtL*xn2>hZUJl9;Sq?m!q(N@UY8)!l~$+*D`4tg&c?~vW|b@KP%6y6WWH`Dt<{hU7Z71zu2efiL~ z+S435gEo7)ub`2y(On+utG^C^Kl?DP4f5cd8}GZKXXHd}=X%b|jn#?dpexY9;4R89 z^>ZA3AN!&{c+U01fS!GslF6u(=SY4BB;S^<(_3}+2HgQ%IW94sXj`~o+u7UV`!IMt ze87>vU&{L>PL`7oRo)bzJ+O2q@6nO;v$_2L1KhU@_vL~o-OMv*YTWac+`CaDUunr> zF2kM{f`40(!H{v~5X~)-P z!P6Zc1oPRA1+Y@h9{WYgV%s zJj_{W;fTyQIj2mk|7W8&x{fIebNo_oxBE9zvIC`>-sMTJj9bc?RlhhZs!WRWCMIkpvf`xaRs=>c8RtW zTi9!Nk;tasSpGQj_zd64SgU8{%a^FzUVo=qWP68ZG3MHxOk_b919z|=2KTN)8eP!v zzTBuA8f*eM+8O1be;5x9?~$GG{!H;wccsljhGv(Ch0DFd#iu*KRkUVo>>rT6LYJYV zhBe=?G;kxA&Rzg>{_J&^mGQ9Jb0)p$WX96K+K>j;yba*YpXrxp>Ck-zpJ1QUp{`A5 zFT%GT?XClxM_6CLDp{bN%^73O@_;ksl|vpdYOWz&9AK<5@9=5sKYDNdhxb7)!+hk> z7U$bvklQlnuZJ`8)_%`P_Dx*TRq%(pR)%>H@?fRgP||-hoM34>%Jl4WuQfWpar}Od1=UtXx4*1vUZ_aD2M%WdGI#m zT~40H)bh|5<#T^vz?Cz5#yGL|OfG<5GKU0P~8&$8{_wsgZA`<}e!szcn#LjK&1rHAso=ef%kn8>>>&-h}P{*T}E6SJ?lW^y$3X(SmC+bwz8& z*>vRNL*$tpaQ?acQdjq8@;hklooua_;|Fdcd<5kIc^!b?R;+x1o^8W$DT87K|SG>1icXTu_4fuK8S(jV_I+?-#-bg=oCyv3{bE4V)S0w|l zdiA7%S9MMscvZ&<<>8lcM)BlHoKNJ8B6npwe9Lv8Fwlv%s((cc;jy1MKh?EqbI;*N z$V>N4RUGu$q~h|fWR2+WX1IGiy1@0cuaDgMKwOt{j8XFgS1=}?W9;dki=(I)`qUT} ztvWiAwHhZG@Bb1Qc8t+ZHs7E95S@ouOXN&Nul!z5^A658B9}9tCr*CS7r0A^@pl7j zN_%MA&1GgS;Qi_o;p3lq*atLTJJBU8%GJ|-UdM^`yn(-SJl~DIad6l5-vH$-58AVS zYy2ohc`f9Hw#)1ZH#;S`wXY}0JG=;VC}X?w_PlS^Kf9i%7;xO)Cp2!=9(JlLqQj2k z9UcA#I{Yj9*F}flY(j^xvQAs$y>!PY+~8>r%QTN$l-EjLfJJ(&2mIbGc%Y?tTGQSd z^pTz`Jf8$#Zwc^nm&|U_-Sce&Hf|6&ZVK;p2aVnfH~a?a^H5g%p=Jj@1)CSqFpFSh zW^6nulV@$wuJm)ji*=kF2l2=9$7+kQ1TN3zZ2?z9Tf7hSP+LKsUmtM7XQ|1qc$|B6 z#s*sH?iSr^XWuhGzbZ~wQar}{r)79pgsT$cc&vj9JyXYvyc#~2+7c{p^fFLo{i!$> z)*%C0>oNM27>f~%JAw}Ear#mCm!iC)JmOs0T>2MX>yZz*5FH@d+3}6Kt-DL-A(&$p z>Cqp?lE&c-M~@`Wb9$)8oD{n5<-VooUXjz!=C7vPtxaD)39``d?Lw&g-eaC)ACIFm z@6@L3!tIQ$&#~7<@Rn(N2K3gmcybXm-^%`L=^t#?YU!V!XrFPL)13pf_j|%?j>l`j z_c_PITS=b#_rP1;G5@wce|%JO$G>TR%Pg-$={MPT^XfhKxih9Ae*<-u{|NbxZqUf< z-DLmBTCI)2!@U~6fcv};HF7}H*^aC37jpPlS*yCK=E?`Fh#E%->gX|sbebFp>q z8CtW>G{{_d?scVyf)CL!{|#-zPwkbD+e}$c4Qb@S=FS7H>!nBbB~iWCk6DkoApIxT zZLq#28>%1f^SeX)yk{lDx9A_6_XX{(L|<51b1c?6H6|rzL9Zw_sBtg(yWh#pz9eb4 zn8&LapS9T)^g^Y|Ub(ycShDxXgtaAp8T_Soqa`wK{^X8;!|6VsK05I;y$;-$lzl!r zO!--8hrZSJp`F5^JiLv6?>Ab%8b6^w^(8u5ntnp8$0BGbZ6?M$Zc zR{Ht@d+UN+1t0IwmQU8d0Rn#qX=k-~+nH3?>VHL2{m$q4ck@u= zt+w9bgb(EIek83r`FrZB-l)APYFlg9YKJ{fHQE<#Y@Cy)ba<~lQM!}H;Ei!n8+>2a z;AG#DeXqgtC=Z_W+m>qYq4G{}<$C!>U|RBZv`2+@b@tbAJ3YCeDV!%IrUMUc`Hbx) z7}+|7c-0BW`V{tNy!2k#fEr^@->+gkv2M81>`m>_gn#CCI!iB^XKphE+10<{#D9z5 zC({ZoHNGa@?!dsN-G`MQdYh=(s1&qM#Dji%6tJ(GRv z-}XN7ANJ>I@7zLvr+OH7UHH9CThn+Beeo}OF`jS^Cod2E z%fKYwf8kf03?&=St=8r{c`zSYb%B4f2?F#lEJ?Ll`@ITYz zue)M}Z^Hit;MY?)Pl&#=dytG?inks3<0q0zs<`(sIBY}ij9#igiThl1uEL$?<}Mh{ z{e0umkvpNSqmQS3v%Y`r7wFsQ9r4QJ&Y5xt!y~|8^e^&WxI-WDg7ck{QLKRR;Jp z=k1V>uJp4TmPsd9yiQ4;?O-jB`L)i9W-WaO=B3;>M2v=XOS!eL_vJ4B`|f+1;0t~1 zJQ@EjYw44^+!MeVEr0h7u^{2}R%mqwedvzKg%RvE;;)j;TdpT*($-W8PAAwALB0 zQ96_NxvYn_khyEYrRg}MP18f-kz~{N9cW{G?rH7QxH?U=3HVF|R_y6F9vTN$-Zz8w zyd!}X8Z^LKaj>?~M(~mRe8g>k?kyZ1i7=8gAaU))|jyUTqNb|&q6 zeql1Km>GJh(Z;kVW@BDp&ZKyV=3t+m>CS{Xoir<-pS|qwp~DK@N6(#4t;jO;yIt#> zg}ih9T3oPYgYBc?Z8dn$x%c^n%%8Tp`P0Y>#rPb&m=6rh1FG_!wI67x zy5-)gYCqD z$v;gxMP z%!w>c0(|M2z57e}40nQu`jVe2*fyXG@yquAvUC%9Va_0Ya@ghLVwVQnfSvrGTN}LJ zo-R`ze36;y#%bn5w_oA^uXg_d{)TTJ%7<@HszYW8nar!~*O(IyfA?YdN`3JseH^*qS@to#?I^Q_S2xI1^X~;! zWEYVos|WuyCc@l)WJL>Oa2h(pt)=!WwjKP@PUK6md+JJN$}daS-uXq~L5C{fzJ+kG0L!=?%cHHiZxWo#62I#71W7?j7Yz#`KTO zjj%p}>{_|cF~%=e91ywLr&vlW_7p#0194vYCl*7*-`CjS%#YJszV7d_Hm13kc-AS~ z2h26n!S4_GI|Vy)9E}&1TS&S_TKAG6H*F#Ra%jr9%jZ9t;*|5;+^ANdcfe)4 zZi;=&f4RiIPjjVHjaBLdesA*q z+B@Vm(6b+UUdlV0Z{VNnPAl^lL?`?;;j6Xh+EwJ^Hz;ks0CN-3LF;0o(O!K|_8OOt zX*6B=&|7s??mp2nYAewE+hfp+I>%f6C7x&K601+z`tiKnsknHcEAo&^cPNegeP&Ai zUiCniBeGah!2 zfgkhoz6ZE#NM(Beee^n|;c)f2)`s}2_~p9-JU%~*zWw;lXzetNgp6MN8U7{1$gSx4I_hiASwr@eR|>HR z&8yL+iWz-V{*C4rj2Z5fAr`{3;AE_+jgJPrNNZeZPLVCAJgpD zGCQfJzvTRazW)o3P5c7w!x8M@8RDkH<_D1XGpob98_W;DU+x+$Dx!9QLu8mM6bo9FD-8tj|wgXv6cgk({&YI+wwzLqtf+y_Um%73NJ z)fz)*Y2HLTsp35Rhd9px>|xfS zk(0B*E!1J}NSpP+*f@c)(ZiX;7Gi+-PEF<8XbyeTzlYwyos^ObB(s-GAFDs+W>aXV z#yle--o{y=(Q!TMTg+s3r5-Z@Zs6ahHf(+u!QK$F0FT?iLp+`=7)aZ_e9(mYtR0%X z*j#HK^l>o>VjYyb;Q{J_S7N>&vO}LHW8?`3ogoW;-D@5lxpNhCdf><6h4(px;~MB9 zTc~jh-BnNfv(%Q^UT+_Qe7sWfFPNb3fZ#^=1lo&V(kaM>v$5zRYnQPvdD-wgf3dpj zcm8jFS7yfzt+T>?nc@4HjOERHgYDCJLC*-L1wKz{^XDV@W*SW3q_uOwzN(22Q@}$p zk;#k!^|v9SlhIH*p8V9?tJ;UaQ?&K@CHBk&y8fGdIK7h}gN%f+IJ6kPGaln}Ue~A{ zWd6{W*WUa5a^{fut8R^GY}@fc>NMI8r>~M-w8dQIM<3&#@w$w8B70yjFu&2yQ8MW; zSPFSXFtZ9bnq zl+U=!`%oKK3wQMH&+pFHjI90Ctr_h~Xjgs`X{{OUN@!2ICQk!vfTa?(&3S5K=42raZ+2?#ru3lZJ>EC^bKnWOHLNS$E1F^p0*ykyc5ViJ4h^5{ zXOD)x_w?}VN(MLO%^pPSN`{~ERc=DC(oZX}#xM+G4+7jh!2J;O2+3Tam)iF+*#*7q zX_7u&2`;bwhj`BJ=T}?tm!#j3ok4V{_Maa(CaE4=oGha~_Aj@J-q^l$^LN-c8S)fU zR(~!1?W522L2qPfTf{eOXF_$vd)7vds2}v4){%~=&MfM%R&zvkj-d`~E=N>nad}yK zej_cx@3?v!ZkdvLD zlelw<^(6BHh>gF+`~q)-Fxw+ui-+Bg z$I2%3=!PE9_NAgnpl^{L-JTxwJ+*>=Js-Nz$Cz~C8xLB8I#uvG-qiPVu5h~XK7Esv zcBc$X;VuMdN4uZqdnu>H^c62?=a&P1J>a(#{Fau7e^2M<8fgVJk}!@qEpg(++^tVp(35um#dEp9{#5wA9-11^_DfK>ghq8&M zKka@R_}&)UrH=sLCn&oT`51^~Q9Mx}xhz)T4YGJF`GyPrl#M}rs!=?_Uo!aMc~e~Q zpZ-el@t@hS$}C>wC0Y)tE$B3!mo*5F(2;alDWzE0rlq;F_qaKgK>Xmb_q zxv~9Ow??cvcqiw%JK5`3NjJY!{)e8wm2TF)CworP&HoslH>R66@?3)M@~334LQF5$ zg{(8%oCtqY->s|6;`?*_e+&JfZ>RNeCx!kuDvkeFCa$FaZ>bz{l!W+={x>R(?^B^a zo4@1xzK1@o?eojavRjj7rjO7oX7?zY$d|me%vC1NPgGr{|7i#O3FW!hwO46$SHI$p z15>QNbaD+DkuRiJm)cJ44%iBJ7M7RI-B^01$&j-N_O6U?&`G|*kxc62zDe^nvKg*^ zLS74b`TKV`f56hTU1o39MUqwN)d-KUPeU>t-pQ|eLnuq#^MwQBxW9-4V_Y~W4yL+> z13IDwc{Ch&ul-oz-B>sz#J#NV3E*&A1lIz=g^rdk75|Rb-dLXLQ~R~XrCUF9hKcs6 zIfRwn*P8D$*ZmeDkG`F~8mPaY!h6ZT;%WL1a{@Oe{CTrIlbNp{kIkFRzKNII`@;up zzP>MyPk+G8*R7vjb=fQP-Iwov4%~zMvo}KPZChW?Si!yr|L&*qRTWdX)V*JUZI0hR z&-(*=?B0uAt?+pOJ=QWXsjs)>){k36U&R@;2FZU5^XrWA@p|!&U$3klU_JCH*1$)O zNvfX$-`X*iYJ(q(@3)(MARYLH^075HF@Ia*mhq{%l) zi=wiEi?a6ZW8qth-ksv!sSVp}!oTe`;otU}@UMLs2l4$T*}86Gw)HP`?}pxK{NJ4{ z8~F&j%cs8}-6kCn<{Hs>f`&Q^Y4yI?&%BGfE+z*YXlpYxwR^g0)5?hk=%5{%!%|QF zu;sJ=VATCOzu$P9$rin2K|giY1(-Y! zHoVCGKQl)E>Q~<+vwoAw*6&S}T^9F?u70uo-ZPPYORnEbDLcpad!+RJ^CtcDK;PQk zj02CO+vg&=dC7v`qCB*Bz7ONU#%{kGyEXEqFWa83#|Zj0pJF|VyLZL=;j@#JtM@DW z%gE7Nke@?a*0KivJLm@O%bnb~hU@2OMg1Tdv^?}tuvb0g$%fMI@iy<@K4tTHVcw2j zYo^0Wls}X5@`JSpq=tVx?zr}GSoK-MR~N3`k}KxaMtAt^J|@uO#&_+fUN{u}bNKBF~u!DV)owb9k^ z-{1`OSf8;p{m^c1bM*YxXl!-%io z&VxaRR|FV&ZvR>9GSfSg+?`n#SCSqgEgBL-TD=1wq60q&emeNkojEQAIn49Hw*x-N zFR}KKuRmnH^Biz~^Y>X>{ce*OMyvK-v~q@)`J{`3*X=5Dsxg)ko1>4gmtgfg!ARc6 z@#%h`0~yvlX3u4Q}xXN?!xFJ!v9N$v3ujTJ96n1_({V!ZG&+R~iu(3TaP zxeI$c8u|U&{n8&%Ob0sX-0&&jqrO`zGl-*A+?WpWFdwH8Oooq_-Eqru{;3AtI=Ks= z3%PFA=5E=4`jh>)@e2JC#ioHL(;m^vBBMjl3vHYshKBn?{DXg6!;9d*ZCscHKX!Gu zgMa+8oF(~hfggKtMDVkQJ|XyDiM)8*@?TDW$*CVV{Tc^6qo0T6i_So<@BxRX^Dh}T z{(8CA`K}q#>(U3lyq=M<=XOLfbJYtx&-dQYW-V&R))E3+AFIc zo|BT-wSv>9zeriZrFpyTn(l33EI9cH_HB%@u*1c5$1kh1fZ9L8z6{}3)*c9+UrN6k zPu?yJU-?hKyx7HH$7aqbb@ z$c8cJZRB3oIv8`@L+~koM_hd^+W2;+(+Rzt5w~xTi7>GZ6!vnBj@;vX#V`TA~o%ANn>RcTk{#N>uavyAT0wLM8U89#{Km}~>rC>k&I z?|`>~#^kG?HtnsVKIhZyUWtr3hwX)@{ggPX-gPL(3jCZ;FuU1qG19=3`kJ)L7_Bw$ z<@sheFL`c*(veQZE^nSu$kV*MW^`L*=cMwrhe-At=!(s3Im+#ILLP@d_CEN@KmD7J zX1>HE{qY1?dr?)*kSC{UhI_l)AY5lK;u+<$vq#;ck$1Y7&AHib1I|k?+W7T z;eBsd6N>oKrL|-H!?QR8{wmIZznU}PoijKGj=npIbKo!I9QeupTeWE^87|Vfu3sDd z1(PK|ZhwGpt#l9R65}`cz`>u0V{kRUH{xmSTlI5*ye)WMW351NNM4>nM%9*+X%`1+ zXiIwD%XLX(65U5zoA-J>@x?+PWv3tAdi0rI7OA7LQ^Y&SSKMFdYXf*n_Z{!`)87{Q z3GZd&gh$XP712v+^oGszdHy&!hP?6mtazOMs?b;BaccMYyr1^Q#^qtRX41*{B6d57 z-WftK9YEI}B+l}bVnoCXa(+nnKu`0{O{e*9OoID)!p+O5>2>smY^~_>n!>w$Y~#0S zY#&yC%#ZZP{POSz-V2TR&;5FyYrx5x-LUdapUm*j#G;WO#rcM7=+X2P{|qjC3*F^Q ze=g#=lYi~!p{!&e?*m%)%b(uoVz7dL2k8#drkf~VdL`wB|5*Ax@8-tE|k~yfxRc}G>%ib<3ewF_s?rPqVrBbQ6+c{JYvz=cE1alHt)dw_jrzc{+De zatCL)XO3%Yy?s0j*^6y$64}bf)gF8g_T!h}`%&I*1sT_S{4ukWMSE;#Hl)uaSN)6n zG`BRFQ+n5;JQgDxUSE@o?xd!S*$>8}c>9?4AK~*z_6D?uM4L*7JA6#`{FrXoZ_<`j zpPHIfpKfKm0|WkGL+o%TI4fQNE-s%ko%q<_#(v^+;8!)q4LSI;*7jS^^7)iedFhN2 z<0{a-?jO~C?cn1Cyt=&Y*8D~~V8HWGY56+~IY+QWG>5NNhjG!Xxrow!u9HORz)L%K zD1S*~od1NFoBZYuVxV~*-w3cgV)uF8={}E`zw&|~la`=Eu{~huU|l4ytM-(4YLut; zl(#6#qg=pWcO)2mlnZ(C6=FKpb!c=CulElFP0H|4ahgTUlcD2g`BPeh6+LzLyyR3g zz4hxphEEzAnJ+0imRQ$(Nhn7;%)OIm9bftdC-c2oq)W)5&8;;be8Jt#u6I2xonO}8 zXUTII3wn<43Jv!%j_m39C-&y4pHcgx=jd_2imZE*!z zXOCMnN5HpvHpJVqh1iIN<>4998T|DzB=$w^@N3f_| zsHbP~VVlNeR8M`R`1a=W@h_NW5L-E18PgBNb`*1v@91L=k1H17U^uRUR+KUL9A12v z1X<%5dRza`==%>|o=WgV_b!-zUtY&oaL<+O;X8w)?ftLu9NN&`ZY%$G#C_SZy2)V7J>V zRqc^)xvt}dfJd-xMSpK#jJ6jl-?#mEp>6K7c_H|4HS|H;Z+o*owFfAgCoL4*$dKTc z&yv?a205f{!3-bv*L{pSJ!MBlOQQ+T$u4~>%Y8F z4!l5e9SL0dlX!98_J2Ov} zen(fBufaR%gaDVy$7NsUI2r$fvrTaNyb!-eK4;olE1z%kT%PBGi*$q8 zHRv!%Uvsa+=JKz6_F3bHu%~Zp_@+&rx3R7?e+k{#&R7pPEfP-9QT-a>yu`@wbn zV(VG7A-qhVV0$cXMP90LsP^v%KCSuid?T`2mkDnlq)%DX4f0^L63waSb?4|klkjyw ze%8S0^4p0GQXl@>zEb(-QLIp7UH-S|Xk&`>gV;EgfA4qr=iOaO^KW=)-0`fw)y{7D zp!CC7vNQfgIyAtc7&0{b0PU&md-?r=+E)A${Cjy0P?f;JiFPk^R4~zZQ6#ztf!)h+on~W|#rBUU z?}`-;O1|XB;O~7`w8mf6^Sktn+=)J^^r?UD@%cY#RZOO`UvtcayP5yxJ(73Y@-n}} zmwVQiv%L+xlg>m&p(D@qWp-Q7v@ac@{4gHaztON)rAEx7qA?ooDWJa;7=kQR)F-hJ z!G8tMg6{+`FC*Y8eLr{&^LO^8WNVVXx1)pDv$<1ydj?rsM*o^z{Uc|`ZT$N&@Ad0` zzmCEDwygQ*@JF#<$n?$tQ?vajdqfx7kN2Y#@e1|jyQBXaa0-3T9tRKY!RLG=IJkQx zM34I?PYj0}CV<1co8XYoBfK7xob&|$&(E8UAB-{i3vWRVG~OPjEy-kqKR=fI5(_KJ zZ_pV9o~w^hWe;t6$gKfE(;9mk2IsK`B>Rnw3EoWdGCz{{d5yC39B|g=SE@SKqjPCG zBR5C1=tNFCT^oTvPYI`EfAwVMjE?|gb%Z!X7+0C@0=7QOkNo_}j{h?Du|KFU` zdGg4n;XB{8b(QJ{_HlJkzDxhSGhMgF`?$be<5@DN{%y}g zw|@t{YCLDipx$-!z0&iA#;N~@+`suD8{NBfW7*22JxW)qde@;nCX`RF@MW&>WiCz3?;bU8lkH(HLBHnP zq-A^$dXUnN+^_65{sdNy)t{1QR;N{P^R2`D2Nr`TY(Z$c?StQ^w%sKh0=Q z|AN{6%kEz=lg{J^DF0HQ58Tf77WS9~IAtH(D~kC6W3rPz_Y2;CydysI9nDE4PqQo4 zxxgl0NVIuBW%a*N|BMC6EpeGMUpfl>0_@fXatYkhJLbRo^?JKYhb_r7m5=@DhJI!rvd_A2 zK~UHB+&8pA8PRH+Xjf>@;vGJ3gQum|{PRAzhia@oavXvVh4(=pW}+{BmeF1E@imXo z{lc2}e9pf+EbpRrb+$gV{g0tOy0h5!WBy$w>KEHR2bp*M6TiME)E`rJygV26w%}O^ zF34+WL;F-~q%-mjKlITK-+m_e{5ykzcUA@+uJ3w?J_CZYP{ztq#>zrF`Q*)IJ&f6c zErSn(;2Y@m9$#PlO&Gu8Z=hRF8|K4`)4YkYK@OYoS9}Pv6+oJKF}`UGeiB2K||}8HbTF1tzRE2)L8Z7Ofv4r)ayj+u-0vRhk2*G zTi{g~KUyPajI8!`>f`W1Hx3)d&_u_nABUANPSKHJOai}RcnvUWU7$8{tAnA*IGshE z7+%Ucz0sts$wagF9-p9Ba+&ZpyZiPgdK8)>*PNd{J>L@{T^rg6x+&L@J+_8!zHy#+ zbuz~O3Gd5j|1x{o*#|L&f+X*QL%DS&W!A zJ|)^y=f&fIFR!~K&`W2IgUdXR3p6%dY|IMYIv&fClTKK}es#k`d@Vctmf>^pO*)6S z?yFUPN7CBXL0NFTek>f@6bGlBZKH5>>z*T9{r(5}nX}Qy(wmHnia)Dlv7NC=M~E7ya#!j zPf+f7;V3>PN$>vkN$I)LZI!>y7E*>aZ0$V|&HlP?E>HNKXI`aF-Q(5d@ z9UherY5G+^KD{@d&(yRR99sy?cCQEiBzqvzS(4>W=DOQnaCe>rS`989y^j4H{X^q? z9ou~;%-!kcXoGHm_Cx3d=3522CoZNoFYTLxUN{I&2hgoUjD1^YX6y^^gOWM%1NtFb z&u#GbQ*jL4Jd0o z@m=D-2U!+;9-bW$JjA-HiZkh*o?{%;)=($5T4M@WUxlsJzvcmfmqUvU*M>X2{#5d1 zG11rF8@{RYqWQ^L>aqB_c?^EO{&4&(y`pb$$7p^I2yV}Nqt}bz=fi=Y!2Msz&z={- zPm@XbDIFZ-Pj~N5gr`R};c341B%R2(tTEn@qv6MHqfPXg#$1T?AKK#g>o>9UHm=H3 zx&MfB5+-ByF}ge0xSpwf`d+MTsMZ=r*>vQRad3z2i{kk&1OvJ?->bf75^D`Jxg&BC z^J!r9bM~hC?oVigk4?2XhW1 z2cJ+~?Tu|LClP0wK z&ye#E2AB%`d;bJr`rRfljmN*(F1<#&KJp!7ee`3u-(^B}>3e@J{vVND+Lf<|N4iL3 z{^39i`IC{adtsdxR(Gh0eR)X}TG%|Eaa2R!AWNh7&|v4G$!)1@HTo_QO|Ye!*T~jO zo(JSx^6YJ~<$v7aTn)ZJ=qK(Y!;RcGFA6*G;MEG*<(kSjeQ`|q*Z(L`Jbe3ukvs4d(nz>*ycyKPTJYSepic| zEB_(ldi>AraWL-~Y-t$6P-2)|m+PvjG`Y8@4B2u=)O#Um{mUw~ew}e69;?1SFL_s6T7R)Nli^?S?Xx$@U@iOeE}@KQRpMK% zl7Sk$Ra>3Rlc#(BhH`;NEw-Nt*#bw#y~zXhyBRCoz0|r>`=`TxD8q^PL(WguJVevR zwU{XOBtMu~o5pN_H*@&qYf3r1OM#b|knE7)-KjesR1bV=ODo8?-=Bib`jdA_H~8_u zzl{gVXm8!iku{4~)0XhixZqo5JF_r0meUV?Wd40^x$7^R?f7H!RNBnfytJ;aMeHSSgU`KywP>*vMIUK{hQV|GNtrPpH2k> zG6tR7>SMaG?swIvuc!VK`juR&PFUXyb-g~WYAuWJ97=ap`;V%uR$LxGxH`Z+re&U? zz3!>6_Vdj{rPUU($611rHvBzOeom&xoBgIn%fm9ly`F)$!?V z+}Zv$J#&xVY3QJS>{9E%EBfYs-RAYT`8dsO$-vaU&x^Onlh#jcZC>mA8avc~Sa+32 z{#Yt~Mqlrwj@nF!t&Z?*0QAZJ{q(QC1s|MoNZKaZFf-=fj~45iPZK6h*C zf4kr~o%-BUss9;DpGtZ7(Y}as=(R243A9bLcV~&ew-7%Ue|8x-@lRZtf8O`8mv5$} zm(Q4~qw@ZIxAh`hs?OKOLmiPXi3&^*N)l&js}P z`a+*4N~a%bpTdW>Cxkb9#T)SEOwghN3}(-Yv@POgG=YBVTfRU<%LVL%on2`2KZTZY zpG|1_6VWb`5xbM{MbZ+xFx)Z8@z=-lP3}w%VmJ!)yajrGwE%~mvn?L4(I^mLjjh{f#vTCu$X*#S+>}mp5dQl+jN!Ez@W7ABU=}|5C1!ry%8-X z|3z7dVJOJLPGGpB0E3-{J3<(4D8k_BH>S+TFo|EWd;-%|1(;q8UDg$0D$+)t=R zm)|bHfF6?knx2^`Z+@c)gQtth(2OzUH-@V~o9WQz)B;>5c(@w$T-;9>C(0|z?|3v3 zk0QQ&o4(~=_In%q*f$F{wmhxxxoy}{Y$7tt`BQulY_05{{KWe{zeCF=|7F3FwP4) zcwE22#-WY(>+vh{e()=)=UFj)HIC z{R-Fj;dm=L1eq?tayhV!<5ygt+b(1jKjQk_uK+)#gJ1F2vR&xbe4G{e62l;Qk8FkH zYaG8~!Z2K4gu&BqOqq{i62Bst768*Ye#L}gT3dvvNEh)V@TLh}#_=mA48xKl46$6z z@NzXCTm{-phc@H*6-O?=^avcJ9bt@{kN@?#%O9&_0D!{#>pZS-SsVZ(p&182jp zc0O1&@%t=zzgBY{-BY5oJo&tbwC2rv|Kfdzkrfx842>SifX93}V6E|-t#rQJCY{Nk9eW2p+T$ZXO0f;KFMqF%yGv@6 z7j8=5LYz>zYrfS017%7U7iy04SY=~)YtZG@o-PLW;b24NBnQWhk{KTpCC;q93_546 zb&XTRuZ8aWP!_Ave2dr^vF_srDbvCKT=Iy6*|})&*;eB$2`=3-m1|EX+4n|~i)WLb zX?<59H}8{NEc7&_?QDLfYB;fmqx$f@TI0QFw@L6qW1~0l=-JMov*${EX2kAQr=9-n zzF1%?AfK{Lwp|p^`j=J)0wY z+<69gI;?Zn7Yn~u@S6>O8+%@vts;)Vv&|=_I(dXQ*K>|BYL zT}!7zKVrp7!|Mxjnlc7#ue_(hbj5TjBR+SC=9;rf??{$apLBcDa)#Z3r01Wdn-^)# zhUWy?P8OZy_L`#Sl(xEYJ}|bBHaPePwzg2t@(c9rIxCr_c|-7U_f25A?P{X_D+%Rz#ir2YPnBA-(NJn!MK&|N%3lYNhU*ouPpSZ zw!}mFGd(RmoB%_Tv{?Vm-Upb@mhJK|iI#)3b2j?CMkt;K4d-6Nc-BcZx7XQ*1gP5uh*M4x*T#3 zF4M!>qK$FMJM9-~V||p`*l$^>ru31aACT1xRKErv)gRAI*D$poKfoa!Q>4kR!_Z`I zL=zjY{{#37zh?Z7@zOX5dTdJpr|?cZeo2IncpTGT=~S`GoZr(LXqU<-3-6?Jfs3&? zvksfTbAdI}8gp#+4h#>mfAj!zt%K-MyLX$pgxM%~k?UUaejAt->tGxY-y!%fgKw8b z<6F<@>W?zkO&;)<`=1-R^IyTI$l@-T-B5Jgd#$kZxb^Z6E)GXs#vyg1lMik>M0yq6FWwOQ0t>H>3UcY~P`OXZ8cT zcNOKYVoW!j5gNEYyN3L09DU`N={fjywM*PMw7!%t|87fTzqKt+C)N3Qbls?L*J>&lx z{eWF#GL>Cb{ejlayERt;{-lfz27ax#iJqdb$&dJV82m(A(L>|^#p0+w+JDA4`;Yqf z*d#yoyA%rhgP0Q!IUm)@)+?Rw*&os46VT&NMGxlIqvVggRib5hcb4~E`JPHXuGts# z)wiJ^;FZeh&Th?_vgMABX8-kE&<}q}zdh>Lmm82i;8|__nsm%kuMhYiBv1BbTz#;# zTzw0&D&EEV;7{r2we&O5HjJO0hwrn-9M1GXpiTK)WWHi^L$iCl-_!)2Hvx~?E!CH9 zi0aR!zIUxRat#& z+&2B2OockJeIoC}IwKL@1s%DHepgkTj?CjT=*TtXuW_k4qx(@hq$Be@-qXM7Nc!(B>PYOO zgVpQEZsH6Dc`^OC9=K%};4`@7`cXc97^8QKUh8OYoufmnA3qK*rwbR}<@oDa`cbq4 zZpmJ+bfW0^%4GBXnxpb8`ES4(URoK-SKxV#GQ#&pbcBr&k9&d)Knw1Hb2>;e5T5m} zC?8V=AG9)k46R-!+(@ghoPUR*rTC~mW&?|jog<`Y4j(#IeM=^eeug=>AETyABb|WF zzCN!5PNLz!H0T!5ponMw&ddK2+OF9AWkz4Nykc|SSB4z*x$fN8 z@#TzXn#XE<&Gz;tqTgx6pKpul=kX45`$L@pSxMcMqvduIa(k2N^3LRTg)3)ri#}Ie zTsZX#M|7{`cG1!9S#oP}D*4bJE|Wo(qmIcg&w96yv2!@tO{P^=XY_f2;8DMm@)#-S zPp9>L?LYkYY-#U#+Os~ef7w;=1h`kIo}QV@Dcz}bqdf15{ijhfuml-cQkQ{>mw~VP zzIwDznD@I;&~>+PhCU`s{=LZ(v`)ke z@vVuRC?4D_{|opH_CCo*Eltv)Xy zCKvXpjI5Z-S+6;KpL0sz^Fz>A>kar;?0*^ViO-UMC4sKo&2x84AGU8en_3>u))2Fu zmsHP?-dY1+%38N|V{C(TfbuH!yxq<&4JQNjypE8Uqk4@T z^}HQUf1932{@~;_X6L#EM{A#SW6B*3HPW2Hr$5uH*mAvZAzm22Zz;S_3-8ruTwnJ| zxcVvoYSa1>%_BN!J{aR6zgu^?%LkUND7~b2wdPZri^>-t_?xXWmDZfnYyfjQD}S>q zm*;Elq<*CTLLZ){aUW_UIqniGV6$?LYfYstF?Sg!JYZD~k%Yd%xEmiNs643@18 z>F(@i4}<52`Y@Qvjz2bc5saSynxDG&P98M>v;IT-qK*0s@NDz%Ls~S7;Q?>aEZgR2 znY`&HN6TA%y}zuuv~Z_hdCuEhT6oiE`Q?A+(xNea^e0+3a@Xgl_=z-ejA0 zB@2>U_l_|z`kkxCc+1}bgXW-lJ>;~>JKrDUGPDW2nw#eJDp9@qJKtZuo?@HmU5hiD zylF24y31hJ{sZYV%IMpuU-;U{TIsZ0&uRX*3O%QBEIpxnf#>sH&nNJ_5ZcYxzk@3u zzl+HyFCV{)J#Uuy@?rdj{H4B4>5kC8?un1u7jJEx;%9_D$#Y}3lf31WU+Vi=(U9(z zf1$Y@HqgmPcSE`x934(4QAU1@@Z%dcW~=n9yNH3^@^xP!@LGSNJ;AB=L;q|1`;h+s z*n1cFs;V>Zf1h*0MM1e~z1Eya5fmYaprW>%fQS|p1sv^Yn}iS{B@iS5M65=gUa_MY zr^ECzBWgRXIIZ?Jnranvdh=EI%l7glLYO&pZA^5 zzxi<3d$0AZXFa#|tY=;J+LA}Set5L?wIz|<@@@4(SI)Op@k+UTTNR(wnQyC-Cw1i8 zs^~(d`L=qY<5%^gr+i!W>5h(kTNNMBly9rz1AN#`)W$qNs>kWw+8#7n+Y>!X_wdke z>7rYBunB`lSC%gR=5n9jw%DGfw*|3h>1k2y89n9Z$DXBE%6qv7SB@F?mSTVUc-A=8 z2hED*FG!HD^l?XG_q=!VGO&sV>XN`wueXmpoc1FFx2rr{0iX8L0Qajn9CRxt5?^_I zYJ0%_v^-n^-$4=F58`m}t$48rpY{fW@80ro1$=7fzXt-BveCu^YGw>s+K|A7H&3 zpQCXW@zz*d&wL;=Yity|(Z6T9A7K8);7Avatc&mbJ9mQlw-Ol6LtB~!-p`v8|K-oD zS6!8wTPLQodyVP+J?p3W`)7vb?-S3=FWg@qOTRpperPQH&{(?Wn;rfx|X&ifli(|p_fad3Y9n+$Ecg~8{yOcD> zI92*4l!v?MEc#Z($IRVrZts8Y?!GfSX;*wtC^b%T)1dG>V(|3SIzRj?@T@%%U*bAw z`R|-b-yWD;lj)#iS3mUC`L%|-&MbT@)UQRb|HjrN{P(L~y7KAyz)R-A+1EXFwC+Dy z!gHB2jPtf+flK^s!_&bNpLj~`NH}z@fVM$-`B!A2_0KO8H)c0I?aL-8n{7yTJkR>c zbHpWGsbSk0vu>Aeu41o%#y|CL{PW8s{Wa;SQkNJa)gwFscyiG1-t**L*^2G@CN= zV#C?Y{VaG1zX9)-hfU_V&t4bhO=ez5c&-GGVx~7`ua!aPOS;mp?%*ojbxmyVY@c)O zQyIzA?eVfWY87@)zx-9f)9?NRSCiqY5*v%HktM#2E-Hx`G)8iLsq~<+!=PoqMp@!K zD@$LD-bPs2*1^jLYF`_J;`UXB&N^o^!Mkl=8%ucauYCbGVEZaVo5^2xxG(K%RdM_J z7v4FIx35rResr5}U;WgVZ(n8d+nuZuYG18U`?9vx(k|T;+Ekgcd0zT!Q`m;tEOs@R zT?E*aVo}-q2-!QfVP&iLp)59~xK3qv|DLkMepVLy&$mE zedf;h#Qc={%!{2YFD|j~SlbTbeP-=Gz{mT{27C!Ix%N6~-|45lFX^Yg4f zuk&{K7IsOzZ~nr?_rdbR&F5=wS~h6$qSpcQx|R57`lO|1b7gSn?RFpigYWwS_30O9 z==1n)2(ha4@jcfjYnTTM(8J;AVbFTz$5!CSSCr_XjGsxIb=^~n2RsdmE3%X;=XZ?0 z7;RN0{qgJQBX2{0rrl??AKuG+AME~TuI{06KA&~+~7!E%+1y5{!N&n?0>7X+R3y9enz9T#ymJuNEH zL98uUKX+Q>6K%YwIhG}^O$<~{ecWtUZUI-7yKvxg`L<97Z&6#g(6xmn5#By+VLtnB zkVCEvo?*SJu>!gmr1?^XD5cHqW-b$mBR z@#uAGpNzqC)%~~6qP_}WGAMk!ufo45-d!etFfr)-&>;0$`|O9FqV`D)V|^8|2J#%y zr+uO!7W?%>R|b7r?AK3zyYFL(-z<)gkFB7;&)BLT zJgUc!t?cY!?alP#D&f(X1{vqLm+!ZB`$udYSy>)>uz5)Qz4ErmAH?E6vk~S~G+yy> z-elUeY%2c_Ln_4@+Wws7ne2G|V&;pObI1(~I(-|_98r#WBwIfl7OmN(qj8+A-(5e^ zVx`W?Hs*3PA0!y=A3et1k0$*kzw}Cv(i=;&h zK1p@$@Z(!}dH}jorw3#09RklM-!BxcyWmywO0YjKdqlhKF;Jf!Fn(9RJ?Gggo}MFH zBIe2HN_ygQXTj_TLOx|~b$JQYn9}hY{{5}C&|EIsQeoyix#!x>dnO-lM&}VuXLffmd zlh>XPw@#1@@xM%_<@I@o!%ZK19P7lt=79#!1(vfeBDn`_fYW)|G^oNO&Pt&rABME@TvH+PwY@8F3m_U(08)iC+p*Y zSKw)1$_K&d4j<#{vr!M<>K~~Wd3gW*1Q{M zz1;}!vUSzl$KGG;YwsVA?2d1{rlNB$K>-Sdu173V*BU-K}$=G%~L+xti0Zo$sI zeVvMZtpT@xK8a`cLPYlFd>A-8E8BVZDB3p?oz=yCnz57m2=&RMBKpinKDRqsM-k3s z_BiaMy)(nUUdcB@-RisJ{fO@6cMovi_hiP}d}nrIqzBuVCs{KGQ$?MLvxaw`?B#I} z{Ak~U`~zoMox{D+MLeSMY@DtK;&cU*JF+L1&_$W}I7jznbI&9OXVBI+2&}x7*^h5<^J# ziKWo$?gPzNAcM!^>o7_~kDN=9UzTOqbbwu8nrTkzX?ow<7B zF^~7L{~}uH^HTKrgVB(>Yv|x{G4mj${nUJ9Lmw8^L*F)NJ>S`fdYJbpt!E1LtfQVB z{C3f{OXsL_v=7d7D{kX4{rWk>$^VU?b25A8#aP>Lu!$MY-q7U}G{^``9X> z*vjKM9k`RBN&US~%eb*%U+vf z_8({;^bFP?u;Dx$`#_a;0QLEH@XWK}R_$BO!>E4RSKRi03XH`co*uWydS@x_l5#sK zXR)0tmm7u;MRxKjJ{B)}?~>T7Yh2W38lh=GJ8Ev|hR)YAn!5vkzhgh(j@iWbO9p_c zY#u-0IID8y!1=-2eWV4r)L^u%9k{N_%LmR6+8iy|%0N5|E_?YPa%mgiosEsHa@?!W zkgZMDxNBVeowM)Rw{-nkq&fAq+A~QT;{CC1>l+zU*mXX6)x`g_9p+=BIGA%G|25`s zr(-)6DeMxsKm6W}Ulswoh(0hKv(NN_9TS126EfKC;Iw{@atXbY&oyBGi)=a{f9yE0 z<8aEn&hg+8L$ULvbf#T-9D*|RI$ z`j~^c$Md#?dY4cq<0$k0x+i`L)TVXs#)rP$z6g%{91iV&k>Ar?r`l;3a?j0fFOG-Z zT&3CaK>5-+{Y9X0IQVkhtA6phKw|~TReP$KFH&F0eDjDP&HGfuu7ZjCjh)bVlW3G3 z;O{EW8*VgyPW}cwn;h>N>kUM+%BHprjr2m@l2K0fJpdmn6VRv9559VIdUA*4QM0oC z6upa^kW3v-!FjfZ%Q|XAV*(mQ+hVVm3(?DkuKygQzSNiL*9L*n+Li389UArt7QM&g zmS$x70m)P}sjnTRzSP(DfhOii_n9Wame4dGnhtZeSn*DD`mg&y8}nPGalmBi>(SUW zW~bI>HRnT`>~I8Y06vbV%~)*ys>-;w%{P1XPO$j~?=KWTMd$mHm#R2!8TCjmdB4Fp zbR=p2r7<~eW7;5X&1_I}*K;G9IEQwhbgsFV!SotvUVHvKzze+1MGPRbJ9zIwd>ywB zeA#*jd?iQjJiO-0#1Z&`1(KC(|4(TCVrLiaA6X5?OTuUy7}qzzMVlE==XSP`YsZp# z;#2qx|BS2>*Ym7&?LTnoiGL(bdGWL_k>>1#aggm_s`^LvxX9P>K0s00@STU7{djyg zy0SVu!I%9~nX*bNE&KOF%|B+GoieWK+y#zuz8ss5=25Y)8ttusuQYSW^m*so{qP-! z?~`A>FQPXIy@S?;o~$n9m9IT+^QvX(Jueejlnw-mY=%!X3`9>dKV$e_64 zWuI~Xcwj$eFPsIN}0q?+Kkl`jZ^-BYrfO8h4!Z%KFXLt%Lav@bNxYNAS{>e>W&? z#NFOr{8(uR^XdcAt9~C@v7dH~{89R@_QSQOZ=c%2TLbIa{5djySGe~Mj=N$Xb-W85 z&tVJ|>AB*#!}mqcgV>-S!<5tWq7psd#QLPhj05U{vCcm0nK8{kefb@~jMg!uZy)^7 zzE4y-ZuH~VO6KVNeM0+jHP1E5#ddywF3-bQo3%9&&AlMRUM96Li^qMe4G4e?Oc#Q>}FfhWo5ryD)q4vY0kp{gct$;oip+j{$aN zYa%vA_urE)oF8>$jP_4MyY@9}%waNypD#Op@DcV7o2_Z9PVzqH;I*$=Jns;U+Y@c@ z+}nG1K275dSN3lm-n7oMj1Q#_4p z!SCmu(Ah7fGv?X79<(kboA-0)+n@#AS-dw7yFJF!OIeM(bJ*bQ!nn}jP6N){(S#Tc zBPpAMM%A@7wtq2=tTl(K^uuEC#?NxrAO7U*M84ne4RA7gmAxk_@9l+s1y9;LlbrFT zcP4iY<9i_~_!+@G^>xvCX?DilsGvJn0;9RyLq$h*duI;#v|=#M*o^#eVLYt4DD7$C zJ4{=u$hWw0crYe;WpbRAg%6djL3atAyN`^_-oP(;F$ZrldqlPltUt%CKe|LaYc8x~ zqhI+d_0^OEr+t4?G^ifdwPz{a>g2jsywGnKrSn{tC(^6H7w0D-eq23Oju*)~J3i<% zzc(H^QLaBZu~r>@?-w{|eX|K_N(NPbay{G!8FAV{9HI2MjWLQvG6C-4Dcr@H-h)VH?>Ptmu_b%aHDM zxleZC?BN#88)GA7^@^Pirqe`;-Izby2Ylu?SYwl(18l|nD%oFJb}}*OWR29$GBuwv zagcUJerdZ>9XYibrw9A)8Fk8T0_LTXnvcg0yzQ8bOLVVwfui!@>#uwt+Y@fhmBEYo zE@Up7StXlsyqo=!J`fH2qED@N>U$RQLt{*Ck-TItUB*MEbB@M>(2uR~y(IQZl-dn> zMz8lZsfgY?)Ypn$@gkWDpW|gqsUMn~cvAi*7dxXx-_(9?1apul2c70uPKLH|g|;6Y zKa#n^Ib&=NLU}R&$@zNc+dQz+oo-~Smc9sD7C=|_vx|2mx2?1HdFMy^(VCm()&1S# z_TC59*<|*!GSLi=WyrYs3#9cU3oZH zueBq5jM|a>H#%c}lf7iVKT#U*?P(6AD2;dTh+CpQX9Tha4m;mseaFf0Cf%dI9QW8` zr}k#3}Dn|09}itlF0BNSq!$lmBW}_LPo9=6&75 zLs_>Le7~(1s4tf7qi<+t4hX+s{V8di6ieH_tp}rTlB(~X&3lih@taLwb{gMp(mu2x zImVwKo49iLl5C}GzuYTE&-d$dhqCYM5YA2*(((F3)LX`enR|I6|Gmt+1Nh+#1<4co zDz&zVT%;R2PfGX9g+3O=Kz9Z0qdO}8i*W&EZqwQYl55r zQCdCpsSd^d(6>0N^@vjZDkmF;zL&(0)+ONW8dp~HmP2Iwv2sdNyAhn$ca(RHlfhpj z1NK*Uv)8&iWWQag^C{*BonHn=*KNs;-28U=<-(pG-fyG*ZUHB{b2g(j7S`>v%tPGy zo7j9uX<4`ak@9mb{p9=gGcTjRg4Q}-WYm1eXTgtbp5koZh)3j?{FIw(Dua)5g*m#B zI}S$<-HAh;Tvhh^eJIO3RgEuuq{*_Ib5?j4Ffq>Xz0cuO`#~18`R*#-bI;FJ`e#4a zC>qic9=^pE%_)_kU2;zFuAS8}9C@ppzD=rYMf9xq=@oC*$Sz11U3%UrJ@agB|Hdi! zoS^%7*@a-rmBF5h%A}`S8EmFd2ABcsX`g0g#tu}**MsdOGRq@gpv`Pne93pQze@CK zPB@T1#2z)z4(;X-7wBxInC+Rb^3RNS#?yAx_o$tG2YX>}OE>#@y5Z$DZ&N#Yp9kA8 zn|kZlkxgx7{e(8q+3MHJ&?j-1y&GWt+9G_K`gYB?fiJCgz`Cm1OUq#GWi~X-2H*P( zpXn*Uer6M=Wbbz0#MC+$Fhi)f3x4{vJ=cDQYTcN%C$;Bx>6tS4tZws5y04%t`QWyE z{50c+DBtdZ?ILY%I@77^9PHs)&b!ZlOEWce`!>EuiVU}BvCDvWTXW-++S~Y+)ddgO zA8V81C!#sdmv|bXb7sir3~=Mg>{jryTdu*R7!KNi(vqavoN<}9fUcr;%%1RC@Y{D* zRR&%LEyJ9i;;RlD= zLFZlQ{Q$EKi`lEGj~Ia%sattfW;4=JG>)^pMJ0K}4OMQuNF2#I5^4`FW?&69N1C@i z;j}hF{Y7npbm`$b=AiD=S_bws^z`N3qN7Xo`SyZ+KL@{Bi`&IMNwWdySG_y1fv>x= zJ~mKayK^jZQF--Uu0N|n9+JT{WWZXu@hTcAH!J%G_?H}^$*1N1keT%|{j$YT)D@SJ zU|4t2n7o_0?he7Shx51`@QNkslHd~$4qqNt@s@-AMGW@OmLcPaRbCFBXZ+YL?##A# zBUj04YB^c$4vcS;8)r0&-`wuZtXoTDWjrAp50?ZkFE8LFug{gr0QvNRm&_der{nPN z(9Rx0Mo0e{cE;g22T3LxTS>3J4awH49NpMOB%4^@eubT8V7oxbF<{=eT;l2 ze#3lbiQm}CJgE54+-D!Z@t~G0)KGn-SN1o2L%kC=n4<>u^o>blo z*wAm$#V)?V`9fya_Xp$q9kzpAw1c`byw|8djPox4F@Q~FY5Qu^r82QthqnFC@G_vj z-XYzxsp+Cye)?qe>g}HKjP#>yzUhbJG)4+W^x#K zgl|kKK5!*z*In3D9v^85`G3;N+F_YG8Z?JOoMyI4ESI-I&4ZIJ`yBtxD1OYy9^yD? z5A3H?UsPO9>7!gab0(F_*V7%0`SeqZ|AVre z6RERN0B!!P6sdxg^z14lCPEf33OD6 zPSRgs4T8Eg=I`G7s|fa{h=#sr?Ne`jKCH&!_=Q`?kX*dJe-AF?vbaii2*08=8_UOM zbXr^U_CWo8U}RH&rftYh{JKcb<}zn?v;HBu{z|-K1I(-Rk?W7iQ+vwm7nnCz3m&;5 z!vtfgUD&Oy$-$4(zs;E1_Dgd=Tk>SS$?pfYxusDia~tDs+x{8JN#BX+OfvtUx!`k) zY4`i5?Yl1?E;+95(oYoNI7hPm^PevR$6RpkngU$&kxPQ3{<#mFo&TR6EBn?yaE{d||+Q#|1Ip1G-x{F)WA@oky-qdT`EkbingbHc=39oG(zzH{(7 z=>WS)wnxt$JZt{g#>bP}i{@VyL)0-wv2TchL+_2@568sQ7=QV1Y82*&k;1IHg!{w-?l}hM zaVI*uP6V%L;hxzHy+k>;Fq!I$!sgr_T@)pQH2bSpT0e=JX9x?OGcs zS6>eLjh5<2CZ+PRGic$hD%iF-QCDH_L|y1)l%w5IHW~+=MtL_+-M$~b=(K_i#h2vM zMcN$Xrf=40KcBT((yEYI74KKtn%3~lCe1C`{G;Z{d7nEt^bmdDYMkkhD|H^Hbl!2? zq&h8@ko<`YM2~&nZuHn$f|t%rH{=DG5q)(dn&0QnQ7WZbdCr%N5YN<+V-2BeQD&3r zp8ULy@G&Mw=C|c<^sTJVY9B=jPT74t&1?%gz1(eG#MwQ2I&98Ve%QCkT`T(buMaoV z7KyieHlK5QcwO`^RD2Fy?O1c?nmfUcc4tqH=4AT#nNdNvczg|>WrqpH0X)-RSo|V6 z9*w_M%<1QGy)Wt6Ts+^NVBW>ba%N@PeLru9`XF#5pu?`iJ9gRakE{M}jQ zzdr;nd~_72Xns>Pk)L4w&G3vaq*d1O{t5A3fv&>dlH4H$IQkB2Q;KVZ;|b|mc&STy zN7MFd(mM~YVx1Oy%zq=u_#(dwo~&+1PeNyNDqZjRibv5z+bZ;bF*@k?d|hh4pV0X` z_@oT`t}Gs)UF9aW!+(Xo*LRO>Lhb(!(K^xD3UR|5{u^1O*}Jyn2h63+@P2`GY;u9h z>sxecuU+6RYJKqM?$e6QvY&Hpkdozu6ph$UoyUC7XI6MHWM_hiXQV<}{?-PcRr#pJ!u z@8u{XeB-0{VvQ!HUB8NUpjez39Gpe*^_k!tC7j}$d~~S!^-kR* zOV${jEWlHgc4*L@M(2f?zT_QI$%V0p*~6=h$22dD{2QfvZ~v^rzN9@%Y722*k#QQj z?iF3KCuEspTmf&puR~VyA$nHc4bCp>qxP}*8@`OMAGn0ZgV2(beGnfem=pRB+IUoN zMYR3~F5xu2t4?R%Y459Dd(c{#w}Ur-5pMlE>XRHW4E7hnM_csW*Nn~rfR9&z(HU9F z3o0tk7w?vr8N(QrHmPr(;d7$2arrdKqn)}-)BV_C8@|o^hqPiIH{J=_(|K9%AD1=w z2q+?1w?*%!7;b3%0W{V@V^r6{c^b@jijT}8`LtrX3hAD%D?DBDjiL+xYdWNTNWR$A zBZ}LkOWC%k;mgoK8vDrF4L+&%13dYU2==qcq6@#~^**LV@6#k>>CNk%I#_dgMDWnD z#l}I15;Zo%fV#2!Z>Z|rJ}?8>JdW@-3e;_qu5+(AkkO`7%zV2g^oO+Sh+Z%cP9OGe|3 z^&e%tF+Lh^)Vs1nC@a4uT_v{h9{hOvDBjOEo#Pi2C#JQ(3p_{bd$r?b>(m9F9QTUb z(4mbt1Vc=v`WF8ia|Dumy=%MIqC@Qk{jhWi4EXKL(Th~S>`40acC7oeitTtDHguWF zDv!Qvq1rC`NkEIq`7UaQLApJ=UZYn z`{vKXtrw_1^5FM;k5{tcEZSLzcv{Zjy*J8vdD_{?4zKMgcDx8C=j#5N>OMZITm7eG zFMskM)OiZeIbvkhiw-Rgk$g2a^Kef9r?rAC?Mb`>wjJNYL@R=Cw-O4Sjrq z@wU;8FJ~@#tZXR@Z}BwMt>?3lzs>|Ea4}x-M~V$1TOxgqwS>_dl7LrMZvXbzC*xJA!ifF-zOx(iG<$fecO$7At;`&f_|+q3)TEZ~2_;5$%er zwivB$tfO~lGtMSw0)MQBH~$-h$B%=5w|zS$;7eR?0eBCaX5t8<-Q^=zJd`|HeMhHl?fe6PCt zn9raCjj4QpfRC_mFDvglpNBuzbDOjAxO{*AGxweVZ7_kZ6dNGl5m~;Ge6;qAFjpU} zJc509$pvZJx8xHq|0~MdT#aIi;0Uwhc>a^*?=XE)Un1{v_?F+SanF)NKJEi|&fy;GaNpw2)~pt8 z__FnKXA{TyG}hE|Zv2tL_axuP_wLjuWe%fHNo_xxIpDZ0`Mv7$K}o&`Y_L1tYp{nn z*ecqb?9Su~-D&!oI1WEQA%750x4kElFTB*f70oB~k!NgNv5R)dw^AkBXndhK+uv)f zQHhUa+<_fLF*otG< zjvXq8M?P>|d*@>8AU@|_AIbVDjYFkBi`@@_CdtIdPx#jNAuHP}GmbV4J(>%Z3>2%X z?Q#E_?1sLUF{hP%#QW5Qdq-d6G39-Oc8u(wk}t^n`^vNWw9A!h@2qn;KDT(EpKq6v zGyQuPyzheEWbmoQIrVSQclSG#*R#VkUl5D8Vlo@@E@j5K=h6NmC$r1{44KvMqs)%z zS7y#;hL*7z?1gorV*U?tHiK@ntRZEwogDXO6UaSFA0(Tw_pk9AvX_kGZ=37@KT4b; z_($Y-(Dzu`gO~BHL;C9br4^>{;|_>ujOlv-d+*2AgWyw7ZGHOP*?NVub+adIN#m(^ zYwNGW)4tgH*CXD`*!usx-o#&AZ%;Yj_ipueRlj;G^e@(*XkAJ%HMa77-K(!ez6HNl zfY0xl2lhXoiPo<(`21vWyv3CncTSjSR(g;*s$&fLw(qj3f7Ccc_|>Nz8LdmC;oH|0 zK4k9|b>>FkKWO{dHsi*Gospq;5|~TZJ4PD&j7V#42|okfon55m_|+0SrJra&-1y;H z@U(#_eH-c2ovQ{1{%AI^*F&fJWyXk|w)al;ac};L^gN8pFn%%^Vl>@rTm`+wXL6PF zF^cPKFQ?{JCiqF;yS> za@6H@quAEqv=_M+1TTBds_%Dr zu!}K1J#Pcsj_>GNSYiV~Wk=Vg*Z?@SzGi-COQ!SZPuaZIcJaXZTh@03Bcr(DmZ*)} zJM4!7_bctWrTi}V))-X%ioUzHj6B(79-i^kNYZq6kMZQ?K^e(|SYF?cS;Ab9#!5y{ z7uQqE;CcH=<|S1x`Nk`>n@n#)Ub4@dBAMLkX{GL$Df10iCJC*Ii9Zf*&WG>LuIS5K z4)|LI4}J1knsYRLNe9#`9fYfb&d&;F74cyZj8(ePfedos#8+*}vM*oxS>~Gig;^EJ zIDw42t^*eMjN7klI9jlw&7^O1__qe~L(v==HfnQdSCe)%w7PdHtc}C>o$!&x##T5w zrIVeK3-MJVJ**>pb-T?Y>AO8vp0g8o9Dywf7x#~9FRjB{r~RYuexy&w&ji|Ur*!TY z9_8-YSCi4ZvP$Rv`uDnf>fc8Fmd;orz;2(D-`U#E_>%Mf6{{>^e@UY|V{l_}`kACl z4%ybA^I(;0ZSPbW$w~W3bFJh_PV^Ht7X&WlF;1N&*j+r=@yvSc2tN;0Gji@5Owa@u}BO7j~Ax&gzqc+u827!=<*SRATE}mKSW@=449rHV`lG*jAi= znA@^GQSH&UE8?DR?+0Jv+<2SQpdII&e@(O>WpSX*xo%F(%J+lZXOTnpLdv&ry_k0{ zt|pECA$PL#WjEt@Z}dAi)`$R=o*@`jz(e>v$I207F@EYKznhHJT%&iqcmbqpPqJm zauTOwXSLW_5NwL;PHDEUTJ@@|_E^*Jn|*yTvag4*ud7ubbjbe7#b?)EQli@+-EoC{ z0A*~=%(V}jQ+u~`J6hd%qc0!&c4Rp|(5Lzm))=gUiGeDkwkndw}0FEf1` z)a%~MjB$oyI`cckj5)=FCr0ud{|)kp6(7aMv0WR-)zfYR;!{6I9G%_sc(lfns$h*} z6l*N(Yb+UeYHstnag0$$1)cbeSM%@UZVaM1$3wtRJGsXEHsh4jqOon7x^$mAB5EVn z1{VZ2#+LuB(tI!DFysUtVB9@>t~y^Bm+5>SywA~hKFgSW7dW~qY5U`7hrr#zobzv_ zdtZj~>QnR2;IMJW0?H^oRdL*Re~K(lq%ED;H!k1vc)0Z^;L}-q@+ldOY3tG*+1nBw z9|(55fA)?YrymuxzrS)v`|0G}mB^AeK3P7mu8MN~+@SL`uA1Mly{*t~dt2wbaj{G9f_LVV?x8J?(0A1G@aW0l=a#@D z`#gLt@SacE>BSBf+Y(Q*udZ6sdCrJ$<~XQl3u9k>6Cs-O*BA_%B*!@#gHhic(NbDw zfyO0?=$o9Zd!(0Pv%p##hu0kU!XclzO6?zoQ$d|BPI?sM78`pzCW#D~V~YR}>oAMk{oGrzI#I$N9SqP_NP zUUYkyswjSEA?8=SJehN|c>rtw%nPXPo1M@OJ^fk5iO{R~sa^9Cx+ku4@ylJQ^puV{ zms%fjmz#&WE2ulVW2b0wYz7aoczA>kBB7i}z_~u}i z-K*~v9j7^YgG+s_WTobOS!E|1sM@8SXBxJJAwpStq&aqnrA zf3wvs$vVo5WNkF$bwOR|BFg6;IYs$i7xc^MBBD$E^*ho*7*d&K@U@1#EVg~zaW*Pfus_5xFuMeYwF}bKM&wtj``*> zj|ax#-j}VcM@RCh)dip0vpG6H(e|GcW7XMvVbeNtqip`K_`BS5qcshK2~HRgCiBE; zKSz%pK|Az2T=}#c#RL0#S+}i>odM%%$irZ#eh5%Ks@(y1Uuglb&Ty!Z+F3 z6L_>YEL$D<>Fe;*pOCGK7S&hOk4)|8+EC)VvPXARqqDp`kQ?+^{A2x3OvXOlf4gfK z^>|&D<^^sJqMi3X#OplKWc0(|O^!G5y_Nf$+}VtJXOw%*!DPSaVBlZ!$ytoc+7^3G zSle2V{bFVoZE}R*)9gF3`J=epT)#6=xhm~f5lz7QIgi{{Xwn*5nR@c@YU9kENJq9; zMfH}`8@Td%R2v#VZ{vw)4wWA$wF~X>><_=+<0&2n!7sZ|J1FecjNZ-Aw-dtD*!+IY z+^|6N$r=Y)41_=I&dp9@kG$`7aWE^a<+m%?6h4xerEgGazolqS)7G?JsI$1_Z#B-) zo(gmq`D5;hZ@QJnm^`{_jRZgO?bx}C0eH^WSvSM5LFRDk>4$`Sq0)f;lHQM0zd&qP z|L~_5*L>z1>-3I6Fo`{ivFG2_2TO1H)Mpl7v)^{h-^M(8{P}lv?3Mh?rxquUy*(`JX0#KPQX#vJT^M zVrTLJ-CA49r@3!Y@NP|KklvR6ZiLmvcOy1{>y2ow2K|1vzeKj~c0#Iou5Gk$2B;y<6ye3tx6{+aQ0p3ltqjz!e_Q&;auSFid`^+Af= z-F{J~x5jjw0qnmER(bUKRV8@i@|^`N0Yx768f051+r_r6Evt`nzAH};bxfw*9gd#g zJrT(vYyC2HGS-j7rh!EkX0N~Tu=Amv@k=S}!N7jb!9L|-X+v5Yl>Nu~+#lG_I9S?M z9+&j4en+_Dy!JjEZoNwI7f1f}7Hr)5@St+6VCx+3e!e6J?^T?cSOx#uKOnpboj)f0 z)Rzdpwm8kX>+0L97!M~jc6N2oCceLQf!Xnm`gY!Lsk_$EYW;8#o?91JfhS(}!LxnY zF&$4(_PnTUp2v)Pf8{6Sovl2zjbP+uw?}P*I>&T8NZuUsH1@93JG&QrmoxabeKFWu z_q6pXzORh!k?`Y5r$57U7kH+DNBg-asNb5%8RUu;;k5}J*}OUDkLPyT_YY4|zG6w8 zQ!bdgfnaq0C@>ce07D;dXNw6xvG3TUwMSQV2tPFCxL5x#nU5!aO!gzItKg$oXOyVT zvG*07zZU0n1Nm<#-`U4D^}oZAD{XftauuD|&^BZP@?*ONOTDrkXJ;m_<=}Z%dGc|N zF1Lnk`InIYYx3J&JCj@waB?-diC^j(+n-Dv--aLFjc@h~KcqS-7hsElYqN2>K0v+q z10#FL!|~3m%E#p}1Gw)1CmxxPoaJSZkRNgQ6Hd2Y7GG1{-j`*N7rsvK437|Bl=+Hy z(Y*JG?a}v(nQuXlmuF9OdIZ+vX8yr=dhrD6<6iRTte~YVzS?){Z4DKiH-gjR9O_Ns zV>7ggBx^`@%*SXik>(Z5HmNiBkh9GJ>ig)_LVX8NUyFDmj+B1m@_Fee;a0x$mA8{X zaw%g6UBE4ihL7tdaX7XmBY1o#!{B;S^^<2!x|u0<}NSQh)^9Wlj# z+za-j;0w^#nWZq}qx@@`^Urc`bA?>RQ;q1RzC>{C;>wE5WkLFy!?VOMwx<&qdoPV= zvlm~d!6ZPX`bmF~*s+xSR#rX=aa_0~?AA2hyr?(Tvol}RzaGPxqJ=jpGX zqyMxydT4sKJJP48`%9wR)mf*07#NF_GOQ~&Kf^v2-e=-`0s1Fw-QFKs==OT=1V;A$ z8K*0QrH#j7|Lg7IIFXPqvjKgrvTYrB(@%dSKR=e6m~zPxN!`aSbb zHgjV#-ce4kia{iIboc>pJF+u$`9a#B+RF4uzI}Y)9Y1#OwleMrf|SKi*GP|gzLGI1 zaWS%dBFGGj=2R_xba8qGeT$_JFHRpGr5E8C8O+Kk=4pRl$Mw`bQgq1=s4YS7Cs@ys z-_n>)@tdwnKNLQAH|-F?LC;+_k3D-%H+y`F-a~ru5_E$s8T)la>txaSA3d7~K7WV4 zTXPvEA12ZAbJdS=AJIFyqYiqp)u(h8h1RqZ$dxkP4Q{=oeWaC(>_6?!XdOXY<6ZXz zj(SPivj_R0$_?(7cFz z(`>~7ih00ob`LF88nZY!Yh&&n`E~kq`8r$Akv)Qk*sS|)^r3o3dpfA+os{pPZ})VI zM$udk)AJy+&0N3$aiwH zcGBk4)J{|uzuw~0)UQF4pL-7<(tB(}`Nn;su*RzQ8kl3Sv$|8Pu{fJdh)3pWu?;)x z*ycvqAN*Vj-^pCwE_Bj03K(d-n7!-pv)z~7g`Jn$^MRtt&XYRxcIa^HGKIa^nv47V zAAxc6a$ot8?#rCT;MZ^*-I{x@k^LgeZ+l-Xd*oSqx(Ph;%eJ?`$vGR#mtC1IysV)^ zx+sG2`XbNt0q=Qzxq7T0QX4?e@>BUe;MYm!LpZMonL~39S+f_sn?AKz*4U=WnmV$q zk7arHVHdLQ8itI)tM{ZtXI{qWwN%F6z1U>T+SfeJ6!GVBtNsf1|588o`tqh<=0>ef z>c~PDvf0~nTevBHY5qm79@%lWCXWZW3 zJ8*A38jas8hbL?dth#N_8?t%L=glbOrPZd%`>oa=xIXT#kGkux-1V2bGRCync$%B< zP@KGtJ)P>~b)R+brs@8!L=>0k{?)`~l7n(->!HK>7s@M8u-KV2-!92cYrTEgvbW8)Ewb>oRcxR!#Godpb zL@P1A;&#ic8*X_z?;#UED@$^l6X`a^wg;D#Rpg)n*l~`f#*c~ z4U6+}^_BGPdMA_g9Pg6qJeH#)98tM;&K`2}iMh&cg>u=O3*}hXRyo6+ePFSFkC}bw zo&KF}d*4%a>z!y_Jv?V-*t4U-&hAk@acI0v7W7`|TjX=Gr|vH1=QKYmzp_)hC+#;r z4S%wOG}B}IF&&4{UTKR@P0)A^xF6$pbiLbMzw53Ka<%?LzF+gXza`8Zxjs7ScDz z(;e)Fc$$;fGSW1cnx_Gt?shc%Ks12sL%y8kBV3Np_Cq2)){*c1v2e9Zzt!;X=E~A) zpLG*EZgF_;6kg@iCKMZ6es*HV=Ux6+WBD4}Ne{k#Wa7`*eVp!3yYd*1(S54ug8oz7 z^(1$l;jYtlb$T+Ib)Tgz={z=fU#0rMG0~No;I8A{wMy3*ecQyle}=vq?UNF(K@_X! zxL5vLypy*o&Zp*NsOJFi9-g6{m-6&s*-blg_Okqn&Jh9jeqc3!sxgqBzsEB%xXpVoe$ZH?v+{WSzUHgM zgW?LO-#Z0MnXkL+9qxKNSH&C}L*nb6^0B2qzs=SN=N=TT5thWqvAF)m{JzOgCN1s@ zZzle@jo(-K-NA1@^l9%=$o0BZ2lCoZKi2*A=-tiN)OW}y{+HIb6!$t`l>MdqUcjA{ zi=_u&ZvShOIceq_?TReiTGvl}yJM~E-hYpL*pD1;;?uHl-*j-_7u+8*>~p8i*SW$+ z_D!zvlHJ1$r;7wEDhLQGQlsb?@hrxX)hb=68YB{GgsqW`10zbnMLb(u%$dGo9B! zzv|lwK6rdJfTzihr<0xi7qmxvMJHE8=ev-1Z)x7d zLf&^u^TxY8XV-P22OQgh%j2%*98ty+M{8|r0s2#{4lOzEMZ0vlinXayJNEXh_EHaC z=~2E%dhMbdHhu(rF7WO8QPn9q(dXtI9g@AbFljfR zB)x%0Z~_v?HofOuLu`3&VH z`zs`S%J0!Up>(nWovd;?^!Bm>ygCO*>AKhV`+)0D9=R3oKtCRKU-aW){|EJhe_U$z zirw#Y?dP7DEK-usDrEe>m0!&xpl9j0)Q%Rs+SiW6`(Spo;FWOcOxaNjw$jRPBfri3 zuI6_gzfbbp!tVxtH{tVMKz{V|RroF6S9|~UCDkW?QAd2BHU^!FJ#3E|Ythz6 z9t4*u=t=z8nk4yZ43M_l(2>0_op$HxT?dWaD~7Rm!??d*_gkWL-`Cz9qkW2_eU|epw@bE89?X5%w<2t= z=T#L_+v-mXb<8N?%4!DZ?9Lglr&n%!W*Ov=_X`6b4cQTea z`^r9q{VxbQFVgcv$S_l&>*KQft%ca@M)%DAO4B+0-2Pox(||0PByT}>1`1{_`-h6wXla3I^QGti_TS1 z9EI)9VyuZxlh)$)@N?fqeug$$MSf)SQ9ku;$%Q_xpLXErR-8KmnU)^{wPBtDtbQQi)hm*-h? zJ>qXzNxp9bq+34gT`J!$nV*P0>fHDfT^5Z$fvczQQ<=#2ojp@;JL&3+>$v8C!)CBk z-3P~cUUyaZU6*=(smYGDvohGOI){$22-G39BZOZtG0F4{DHvYYdzZC=&$R!;7t^0XA>-iH?W-zQp- zgJ_vqhL(f&ftHsfYxdRjY>v?~S8^H68hNBAt(SRzOjlPsT{R$!VaNjh-?|?>JioWJ z&a37ukw_5<`=Cw?qmXRr@-eydEKv&ZZGlIW~IopbpB&pT4JzX9nwKc9zPgovQx2;}Vy=?l_wQEA9`3wXJq_W6Q?c^-Z-Ko0hL=Y-^p`5ClG{X-!kxq+?o+ zO~3#B>1n`UzOJ!V_cKnJmOkc~^d|Rs@^oeLcjnm(7tVu#rHjt3S;+IE3zjZC`&^|& ze@o|G#N2fd%v(6`uXMF~mbI;^ZEb9;-B7=-seVn{q-AZ#r;X4#OV6z}O!@lD!?(oX zeU14dswS!@;;(ikEG@hA`1A?WrX8O?ahd_1U{K}ZW*(oO>9JY;1K@FDD5|fuvAMCK zt#SFJWsUi|&Z}9nB)x2XTU*PTDUR(^(+%qy>)RTu5k)mLRxfXAtzXvMxSWIt0W>b% zw6-z5vTz@}`zpMN&t^qQR|Q*Rq0!&>DkiOE)*IX{=v2 zxuI!Y!}{j>NP0mq*`!iTr|j|4`enzbXPk&QPMXn29%SZGJHak(YHMx;*OK}Tx&-r< zM*^>(W-V21v;G9#I({OX+535y1N?CLe2ZOBbCtr)F{OoSL}{ zgty;vGiDSDQisW*&EwBgyR5!pRdszsTT{!LNM2{PtTuWVHZ5CMziv~oq_M57Y0ZjO z)uq2R1^U-S^ox}>)5Xek@{OJ~^&6U2)C;M2$*Y-UYEJHteS|nxxm&coj zmxw&LZ{Nmm5gn(d>zDhcnm7O8yyp5%%}uRQW1yb%*Eg=003x~8FUu=q8Tvm_4(X(m zBW*;Ai{xr>Wh*c|s-U*DVO>je^Q7j6SSxq+SpMqz%S&MJf9d1WGp41dq)#}-G1ecx z6Hkdee82fKrx}JqKH{rwTC=?Ia!3+u$ldvtNt4ujOi9l;X<9sGqf?#FS^2=p2#dQ^^D@aRq&$PN#E(YPfw!)=hA zJ1L@(I>p{}aW_3;jvQfk?WDQ>icR>3*0xjA5gqA<=6d?+4NdFX*4H;TUC}55ilkTF zR=>8kzRmRk=}8SO&6C%)Y;@=f^loTs+-RMsdj8s#lbSK~nJ1Yo%seRw)-|r6xzw(# zZzbn)2s`1VHgPveC02^y8Ir|x{K1Uqw{~6A>ROny>k4;Wc5M0s>N+ESVtCaD zTz-6d-L%@aO~w=xw_J2_-Wp- zQFeTi@zIwxGci~@1L-)f?>=w@Bh zij{3sR=2F9hhN)td1JG8XlpO8RW~qcIu%Faj-)-A)__18OuP-^kNaiE27TcL*Sbt_ z{0|)e1DCf`(>qa{+@L9)Ys-29D3mGpFZUTGT|Di>g$|*(JOH%~E$a!rnj;tLdJS){ zc{u=Rp^OP+B}_Mqag4}OATy0HMdndLCsu7V7Np@ie`igx_GNsj6|}4!s7Ns%G5D6X zwXJRS>qMLUcO?5ls8L+bdjrS(3DZmHj{D9CW(J;fri|?bfcY!NSBB5XsZa5O9Sq)E ziR+q~Dv$n3?2?M43Ue1NYx5D~lr=4FQ^*Sn_H;rd0|+i>PJQ4eBQY9pU&C{B^lkUW z);-BPl}Xc)6pjErOh4B7mqC1ks;KJQ5M(s{(o-UryjyKrn_km0rMaX)+#({ z4d%A2-6Xtc&~VzQrL_>>w8rBzqd%&*x^-n^BV9vbG*ByHwpJ5g*j;+kvgVeCRfJ9H z)|GJ5x~ggI+Q#M9B^dopYu2uBt8H#sv!Zsb;?ml+iVO-&c={u_wY0Ef+{i@UqH$`Q z1{pXH3kc`^RqfK6^TdPx2E$)u-}{Px;i7Zr*Pgd{9z!Clzux>5{ii)G(-WalY5g&} zr^V@trJMIF%TKH#y69L%Gh%TJL*keaSGPtY1m4M^e7;3Okv*b;DW-u_(`|GSD;Tr1 zu3u{@Q|Q;xbj!LnyKAUl)6m#V53-@Ln*Qa|rWNbgY4BIwvbJqX(;7GKSyxYARNvgp z$Uf}`>#kdBWE_LhH#V+GFJHfUZEL!%B^?|4Gi@0C)ht|?7H#?G=Cv#9d6?7Krb{rt ze)VeItgffhcl4J`RiskMWQt2Pg%K~Es7MU-m?HD2bFTQpH}Adon_sx%oTGS56$+4h zSn~sa4D)|~e4zO-AR>&cq#kkoZt}X{+S40`y?fs3=3)2sM+D^Xc^$?S48Mtth1CDytu(G7%=^r}e&=s?@BYn>&t#Ye8iH%u`eCy5mxPJ>pNjD4?@@Nr zD9!#%-V+{xptmfy_btf1cYGjyL=cI@u%CrWedFglcmCqdFns&1@abVD9Ro-N`%8X5 zSXN1aRMqky^YG&Su5LjjgA1N{yq259P~NExf`%u4dA^&cQnvoYdQ@KuBneluW$8bN zO@VOQ##P8vn)B3q2&hR0wb1zdG;RvC`Fzcc9sC+P4)0HtmW^Oi!R&Bv*xR%Fz!>F) z;$WQ`?&;aHms!PvX~pu9Xr}~tr8lg2p6*%MzU_tIwkOM%j-U@N=C4bMZFnr*E5(}sZE(pUX4ha%9Veh=JhT+c9B}z^Xxj77< zJS>P$}^S%O1){ReKmdGOi2&r5_8!U-b6=!T?I?PX-4+&TRSVVR-sh01htUKM}l# z?61;-!#Tn2J>mA^8WIVPe+h3(B<63{EFa`FuWDM(0}98VR%=Gd=rWH z+Hfn@e|Y7UKfLtP8+)(o zo6iTW&KW&!%$PBw)4Riu_06vd!w*FC9PnazRoVPGL^+9MVr+l;bCW}ekCNm1%ReQ; zKlJzI@%KJ{zx_Sq#86^U06&jSq!gabGf+hWx&Da2Y3C5zPGI)rnMc<#S>9^r3wK zDuPr05dLWt>4*Gl82)HjERsp_Hzb(#hj1^SpAhx8gzR4};eyg(_{tbXxv3$K_x>qe zsNuvpHFGX{B@B0kuN)MtdGgRykm(7pbvjN37fR*)wX6?cJUF;EyqDPhhrN%vKCmMA zc*tNQ?7bqmTKVlg;kiNZvGBi-j4G(${*|*%{lE!9((Q{%UAsq={g}nyat6>hHtw)gOc}li&URAUWYr;paG;7jQy`J0$pM_;#=49}EfBhT-k`Y*O@B0YLbtF-l(F8~*O_ zKD}^7aOvxJG5VN!4}sL_1?@z|`9B#Pj)OjiK}PoUQtSqV6qCXDSKj#iA!W14<+!(? za-zWc{geF<*#7b(w|-IlznWoOmo$qQt*PL^OeS4cbaGBZgZKRmBRMA+!b7o$B6(GJ z(F(#n7spUWw~1ix$F92ix);N6!`W|zJvUu-)m0yg#mFgHhW%}10^{}Q_SU?Z6Tz@u zy)Sp|%01f>4Bs!v{Ku}YuHWqqr+OtOf-x_Ke=~CQ0WNge@4!(bKN$AT^^7C}dh{!0 z?4!=vc;$!Km6eDBRVRr=;y`-r#IRslR#m_H$srXCB*OPI9$haXNUH@c&ZVQ2o51MzJ34HBfS(gfW2?L zH`qW$r-_5)kO#x?>&alk9%L8&?F}ypf{iq`$^5&Mo3KDaAa9@#p=q$$mK5VZT5lp}HwJ?0^hvx_TRh$uqHGYx-{ro;$Z?Zh{wAmBv z=EShKrl7x3FYmplZR7e4hg#mMjT_qju_w&uGw|PGgojTY&dkhh(fw>$lewOAbzmU=HJr|d~ZGc-F~qelEHaT z{pdqbXAA4_mddnhS8n{24n8t)7Rrk0>wBokLIX z`#{Lr%4-Le%_Z@`mwI}cf}ia>USBY}5{7l{WdgnscopYU%I1IW$p>yF`6#9Rw=di} zu6*?o@No9?5AX-$#erujY(E3jQn0@~|Nm10<>RuvEYzq5u%JXxF-GIte*TidjGw>! z%01)y!H`Jo|DSR{+sZdDQfGyGd-v}BT|bjYmLK-?n{W8{?CBRjZehkR!?5$9e(00a z*zYvAow7WOgUAOAWtNW1qxwtg|7#NXOZ@+r_+K{pm-xTmmifOk{`d1N{j%kQNdi#* z++2r;W@-22s8h^`oQYV3o=6ORf^#B>%7`CNs;SAGGI6wu`i_8nHoknCS@*xzLN@?cnnoDMvalO$cd}*ZZ-QZ($hKWD)4t3uo41+h3RY7zcR?`W*-$G2QCvL?%Fg^1 z6K;H-{=mCl{2J3v#s$kxurrg~AuQH!`+cWg=MOlNhmii@+n5-kn?Z`<_U$AVzw{nY$L1nh(SWKd=M zqyGImR!%kt$pdx*koH=oS56MOn_UyLgJ2p{uvb9y-Q8aw9@8@$h(2@{aKnPBzYD`n zARe!1pyW>lhMw}9kZH$0LljJc0^@Fd@#$}BmY4a;lEOSDlT7S0*NG(8_r(CYw!bHQ za@I>B)2V%`@cV;a3r>19TR#?u)z+$h%0` zhy4eAn95i4m;FC~+5hvG{Xc!YLcV=OH~qIC^7oG&+;c?tvA9i2p^P%S0 zlMv~+hoky`Jcfspj*StCc2dDOw&*?8+xrxID@IEJaXUy3vC=o48iuE`QQ(JTlLR`k zKe~R9egaz)S{L5D*^^fb#bc+Ob75PGp7Vou7w>6!zD%_`Wc_ zXaBhUU{_a#;d93ld!Nx8o;K{>z2QF<*&hH`6Btekk~6~I)021aRTC`|Nk!1k?x2Z5 zvN=4H72-br1q|1QVb?L7$H4mu_m%o5?5`sD8^#gu;|tSrUqu49ngIBC82*;H`vPly z@#-UhRPc$n!v8v&hdA>EIL1@I-V^?&%0yjA_IJs|kZ);r|JRkpyt({jqT(*Z%pQgR zII0XlGLeGsZ+}pG2!8e6J^=XkZ{Qw>o*M39i^wxamH;q-VcGq`1A{ZdaK=qN;V+IT z21o@Tq%`~fX}?Rp%K-4_hZg}Pg2NyB)~Lw;<6jv^{_LeR@lPK;znq@RwLhnUl8X&P zOVa+#NBWh)F!VLde2HW*_uC)r$GBvXso;Td&(g90dN+iB_wU`a_rkIOBgrM7VQbID z^j^h($>93uR;qP>KYad=VQ;NzK3UjE z-p|OdC!7Hd3$EWk--#rH)Bd0l<$d9O!LmPvZyir#d^|n`wXKKYrQZy{*}&w(2MH&F zPwaV$y=cA`K6u6W{q^v_d&57SX^AKF>^(k42LRB(-|KUNVCGL}1S5VMekd*q2CccC zp1r#=LB*`!Gp`Wrj1vILVZo=vw+Kx3XP%^+-8SE2OH-_qtq4}V&FaD1tAh$Ym&*pg zAB1aT`4vHvRUdLDZ1CQ1P7X#r&xv(;bFjU6yK`ao#gMZ4J1zX9+rP*I$f5kCZ-n2N z!MJ#pW;eF(y*JNZQrZ7qgZ>@}PI$E^Jc$B743~KHsi2X_=mSCW-tc?DWpDJpC0~Br zl6><>1ljNp?}x#A!|$>!YR@@}5EAO7~qnpwF&e1rZyBHM4yia=yt%HOkh&v`Mf z^O@@5BKtePOm+m8Qu!tQW>Y@%t#kU%_O$vs%Unx3+AB(Dy7>Ku-!uH4=Jz98I zwe#D^Zw-$NAerZZwS9`G{pCpgB-t~@%s_K@9|@2T(FJb z-|_n!ejE6$m>C zT}LI7BXzChrG@|?{o{G`=+WdK$sV;aV~+&S=%YrD2EScLEB_FR2IN|~14RaO4Aa(% zu^g^C_Mk%#8apNc3xActm?I88Sop^rcCcQaP<{LzX!QYL&j*h^z@AlZ6mN*|Qe{wS zcsW4T!P{kjdp`K!BfvLi|BC&ES2t{FF#f2o_^*Q94$3u0<}>ZOaaja;-bwrOuIz;6 z`%j-aQ!l~VE6ryvI(KR9dFM(8h3B)+xQOTK^{wlwm)Ez|SFh&Gq1NTK)2h$mbgV^7 zsxRdnqSor=jhEK*Qg}7*zw1;VOPy*bgGBYctMY5ttcXgx`de4luWPK8Rzsm0 z!{1N7&=mMtdiK1_+UEU#|e)LPA{H?6JJYg;xpa*7l0 z#W!!_NUyfG#&w&jv8wuprZzh}L6`=E=d6#m_0&0+rlZDmX2fp1+!~Hm+t^&cW*YC* zH?C8GMqVXY8_^lVzpQzEV_Qp0+sbOmFUpS2(Nl}W+G|@mwaKM(&_>gxwQH&ER1TWp zzNx_urb;((W*Mi&ES+;kdeX+Gb?$hEV8XQN%TLY!7X%ZQPnaN79^YW{xARtjpXz+Z z)Q_$;-@&y}HzHp6IFGCmF$Wtt1w;u}&S&~(55%+AU)#z?GlZh&+SM(~4XgCu)&h7j zeC_i3b*rM|R+Oi|;_{Zy@j1x5X=-cRCOf*!;8r)SZ;}z|8ZRg3vtk`b(ojZ!UOs2c zn{&Z@p3gq_%tcDSux9bOy3Sj?c+q066I!Qobce3B6P8D}%8uIO!bS6IYtFM4Fu~cU zwKJukakh#F6FxLy^@QcM6V94&&IBEjFo9z?)~;=><_cb2!5hcNIfnB%=&%GX@Kuv; zRZVDaUZ1{rLhFaq6I%I~KaZhIlE?_$@qXBcJx2c2&eN!F+<VD%tU(80v!j@$Psa%jZTj8{Lig9cdqDPWT(CH_2TI#&X~y&WV4*%Uq;(( z+qAx^#kG_A*5#M3SozUaQIsD9S6tq(VcDA1!IbK$rye(c^7~!2$fMS7mz-BKcOG{h zzxr7HoxQq#MWb3wy5-Vz{hCebRcl%{u1V|U5l&}XN8hw=-TJj{!J_pB5%nvSYi(Or z+oZBnR~nvmO;eXOwQ<79|Ht0@fJI)G`@>)UsHnJ&hKfo$8X76C?21Z8%I>0~qSA^+ zN)3Nz*ukBJ5r$oML#3jmqN2nO6&e*4r?#Tvbf`ETDk?f{Nhd1mY(_=JIdKX-TT!vh z`?;U{^UQo_0MC29?{)pI_j<4SUf1xM@8`as`+1)Gxu3uDodH8gW3axZB@mLI3n}q- zb$Pdt?T~d>8`lT-waBk*hXy<6S2z3b+ai z3Ld)prbhHTUqeSzQ=kpMx^FTD1~R~BD=#U(G>eqHISuD|Ann{H0J;J*V6_@P9mM(vgB&$;2XlrZ5 zU}cJpP%*&<9UCXQ>eTqi1b+%%r{X2CA|Lslj+YD$XW}K#vP%5OGlMV2>wLT}!VBRd zAFw-D;dL!uuf%H)UNT-a;?<7VoALT6Uf;m$*Lb-PG;#aCUJ}FZ7q>>TJu}RO zUQ#D{*$aEM|4&_~{~x|i+v2iLIpu%3?#9ef@3p-NUd8w6Bxa-?d~9qY-8jGA7Mc<`=t6Dyt_bF(Q}5j$F13dZ&N4(vjGa zmyI(Uf7XVTZ>A%bV^!EGGhXZtGn8Je?gjxompp#%xMt@y#n+Tvv*UW#Kze)mu4?># zp!o$?9r`#p?|l8WMc#(P`2A>;(I)mfT69hqQDYEyW_D+}Hr0;}<{0`zSOl0x&;$p) zrTDer>vdekoNRuw%MP+7@$q%H{;aiwp9c2Id%R4x$(3c{wv_L!yv1a^b$WMU5160y z!VkX5`UI14sI}hjt8ek+hu7L8_;uh$`4P4f6!(SXJFXW6UTO!su7g;AVZSaDo`^Q% zWsXr^HUYjM)(bv6Io)yV?YBz0UH;aF0C4YZyXBVp$PmABhb-DfCKtZP=wzLC`l)A} za%N_B##!mx(qsm5oVTID&N}15Gnl{mp=uxYAijF}0Wn{oGmPq@9X80#Z0f@gml-ow zj+IqMB;bp*)wi^VaoEw)$eo_8-$uF4q$wuX9b|eFQiM904u?7l3P`^QX0F{y8|u1C|2u;~>#mV98(Bq7h*B>RNOF zcnmlW9RKTD^cc|dx3%apuo{^8V(`Fx;4xq+&~xs|s2^Ae>;jep2Y{8pQD6`_1?&gT z1E+zjzt*U4x% zaCpzj=rC}!9R3FO-g+{+035pw>9N4*t3Z8#^S7UjmH>lyz%RglU>LY?C)y3@tvneW z2S$MS@X(nDo&eU|h4lDOg(JWcU||)~11t8z55VBPC!>SFao`v*uNr;;jsjPJ6TnO? z>Slnsz?}Qw4`3;<0_X<@fs4RyVD5vc53n3K1snh_09Syk!2DYHH4kwO%mb139JIH0>i+(2;vS{0UQMmcOY(n#Rp*rSOr`IP8>mbyoiHGP#$2# zqo@yX9M}WQeJlJ7Tmeo2kM_dPz`D01ZZ1XoKExfc zKQIKG1NH!OK7KMf1S|!P1H-^MVD~3b4q(+N%5gdD0gHio_!X~8U=7d@902wJXMsb& z>`$URz`;+UU4V5*VFy_K>66jyD^QNlAl`tBz)E2Fb7)`TKachW`o93b%KbR{6R_Z) z5Ffxkpyy?fe-ZHkEcj={2XF-#1|FS2dSK<3kRDj{WyA;Y#3aTQVBx=@eG3qez+B)w zun3s>RrE*T6tE6h{;%kdz*XR|+6pCDsTlj_#M>eO0?_0p&Y=G@1j1y;$uh;%=;eF1D)?9J+L1* z3(R>2=|w(^^w=n_{Q&8Kis0Xn3$EXLe|0nQAA>w=< z@eU0B6!idR{tWd1jsQD>bHIL){|Dj&Sa=-qA@{&#;89@q)hPGR(XPPmXVI>}>R%wv zfc?N8;39Ac=wCp500)3`z`|c4K7cj9Mx1R&`G1Qz0}lQU@e1_*C&n}2C~yGi`8~=3 z>;ldM%a+j|z#d@kHE2g*5pezw@CUGL1>-C*|2ecPaN&8hE6{ra{s2aR^T6ys!4B{! z(1U$t&!15a-~zA`IQAFR2e=IE0gkSs{{k0*lfdbM_2@FtdHH%Ya|hZ3m=9b9mIDi~ zT95jHIfd)d9$-0e1ekgCdUP6ieEWKI5xBN<+*k}ng`4S76A)^<-mTRU*y-p zj@$!>fTO@k;4$DFF!Oqp2bcqt!-Rnw@aR9VrWAI7Q$Rm(9@q)&eARk%5SWKYp(cT& zz~jJ7{G!PTU@0)G7=8!l0cUSrkCp;!Z(EPn0?Y4Mk9Gl%R-swx3HZr}oN5V%;g9-RPsUb`Nh1^R(2z2Nu-BAHbXj)B`x$xE{^E0pp2(Jz5A12G^sNz;U1-*tH*i zmHXE9=ooM{jQoK`52Jm6rNC9-abRu<{MWV~Edd^DhaZ4Tz%Jlu1myq*JJzE|fdvQA z&cGqyabR929=ZaK9fm(&f%Z89e*)LufPWweEa_U0Mu0uQ0pJPXI57AK>I0kto&c6V zwjRy75q|GReSqFKqCUW^H{s`=fZ;czKEM&+2r#Dyz|Je-=eMp$ zi-84?uSaWu^KVDH0E^zS9vueO0;hpPz(wFHa19vhL;6yb16T;mekaNS3;{#JziU0( z2ONDj`~mFkhaHi>2X=syz|2>{f51Fo4Ck#Dqzk(Aw94V*bD3ejsp9E zQ@~N+Jn$HB1-Jstx&?m!BE}P71+WBI@Xzooa1_`H%%6ZifNQ`BVA+>p2iOB#15N?G zw?Ymq1D1RR;}Nh2*eUo8qzBFd$AHJbi~0aP-$yxstFx%jZHT*nhd+U{KZHN!{>Nw+ z;M7l0AK=hD>H{qKDcTj7_j9xdu=*FMM+NHrTcihe0?UBM|A_R!@CwocE1yIE11{i6 z%V}URiZ}&M;$VLbSbTakntwa|a8@*04)nYz8ubIaFN#Kcfy;T(=qRxFCDG^%aN-g? zT?;%8bnZaAc%#u=VBclYXc4eBKN_t7t^w-=uZTuFfn_g?M*D#g;23ZmI0Ia~9zR?K z?7^e*o;%ULC9ngW1y%uTUJ;Fkfth$zycg&PjsnYWLV92bi2t?fi~*fW)Z^x8)C-&i zmH~5ip&Y>M-O*?_um+Ee4+Dp9jYg+{^S7fuz+(I^clKS-$CLksK>ywNHE!TBU`X&D zlpnYT90NM9#zUjPN#H7QVsA8>cQ@*XC(p}(BfvVKryBVJXMlsi!Ta$zF|ZU*{vDV5 zeJJ-mh&Mb>=LL>Eg!I6v5Yht|ft|oCd~dKHSPmQob_1t?m0{E$=zkRDuR^`>nM@(D z2p`^50&9UGV9uMO(LUe+@F=k4&G@}8pgcyr3@ij@zZ!nVQ^Wbd0bnVxAK#L!1@=9T zFVO+RebMLuaBv9u181K?{=neVsK;LLKrb-+L#PLE99ReR48sq=Vc;;Z@WV(CoB%G! z{YQ}gUg)7na-|*F>tuFboOV(6C5FodSNW?oJZp(-*J&4vULjtC;EII9C0rR^9eJbB^qUdit~$HEZl%^t{myae*@&pFEso@hN(!#yH3@r zh_)&3B=knoPDZ6}lKvK?7yrzG9|iBWD{Zw*-uhc~opg+OaT{K1BNzNI_yW;^k1JTG zBJgA2i^)sN-s!4S739;98`~(;9+pX-`J0N{D-wHBrf%@F;N4{!pbdBV#okf!ER#oU z2tN&eE^gcK;*;ax7r`Gxy-^3#Zs`weVMV?Md4C4>DR?h(6UF1XHyEox`z>k0*RDG|Xz8M0ae+teAgvTZR8Ut4h z&MO2ismC<9GI0J(+%T>)&QCKX75O6MJ*Q*ei1*T#(q@&chsf~&pECrx^|#cs+$GP4 zd|(T?_`e+TKFEvJzUNMszZU!`cqzABf?;qYNn8)OVHYQ58U!b0syhWYyS9y`J$2sG z=Vsh*DkXVNKz@81_P}@#xu=>wo&}%xBJ2gp+p%E@d=+>Z1E6nW;jpWYS;!~PgEoV& zAlEVHd#WJiDS*7>#V4Z^8cQk<#pM-{&*mVO5L2=ApNh+ako(V%#foVIJBEn;UdV?b zpTc`vKKIx(3VB&B&J@&sQTmth3@KEnz~^3sGbK%D#%35%0iO%L%4K)= z6Itn1X3EtzS%yyePWfd|TiP5gRZjIux^rX0p#WQtfS8*ucutbwF=0` zAa{>-wcw7rILSW@ZWNq*d=kDF{BiIFYCkrI4TE2H@iI0u%uGB?L4E@AZl-%Z``3bGgKyBb%h1NJ=aE$Cy^uRj9M}+1F{IMZOGq z-7d@-?%1e~%d^i!eB+mIWu1idV`*kVklraP0xzgS$o=IwkHy#l`Bb{D%N|MNx=iw@ zf=<~TIFnTy9`S2BdVq8?sY_V$xHIkt@nsLV0T&0#^o1etec;E1*kpZzf3#vg7h6@i zsc#qCNXl{?^3_W8CCW{4eew0p8sv3%p*~tx*5?tUAnEh6QJ1@8h2Gi{KZ#8kPp8a=hl)Qm-uQ7H-q2` z>EHC*w;6s0{L#JmEJ+lx9iC*a;^HosFM1&_hP+A)qWmJa{Y}!3LhgTo z^fQomL!R$SZ|7U_+Y;nGkQY)8x0!OwkJt=?XP?mdhVn*H{~uk|?*W|WYy0Nw;Dug83ac~5+r@sl(#T8CA<3kUF- zf)JbwBd+$yeG$?&;WGn`Go9ZLsDk8O0{Qel^i9e&c6IY0@*2o%n&DT@V=8~b6kg<= zkoWJ8joWob{yMFjwAUcyE07Q2y_7wG@=Cff@R1Nc+fd$=%jYWhEadW;ZL-^MuWoWHLa-gMX6SV1I|7}W)~)lMhP(^%&Ei1vT7-NC@+r;3+kzSi zzXm=W#^-M2gXDA02Jvugo#S~8cvFAymEiNWO}iPdZ?*AS33*j}%=e}bTi;9i5adT8 zFJOAkFOS>weUOiL#MZnVkJC-WOTRh_dEp_9I~sGw{_btVnHU2-yVwp=FL}(o8A_52{u@eDv-1zohQi3dBTNjbY9pN8C{_3@_f*0Efk_nGX*=enH37%lt=_!;n}YS$y@ z2~wU(@XK-D*t2trsXyddJsZ=V0ACpA6Vi#@93-xQyieOj$G&>4h}bR0pYgZgb9*h1 zH%z})g7?1_pA(aR1Iy_LU-o#6H|vx)xct%sc}{O^tTW{aQ0|4T5y<-?@6&!`((iua z)btOz_BJ!nS$%tKJU2S-@q7vLv3Fq3(mEM=IwviubJn@&UwyHCw5hXuA6)=>_?_4f zLKm@Y=1Fb`Oh!PmVP<#PEx z{j-`eT24nkhlis}# zFM)g+a*l1Dy)2hJLzn-4oNH*l#?L%1G3^kB-1z|3wCc}U&LQ1)4(J!Vcy?&il|IOF zi~q(Tj|?U5yQIGIY~BR;Y;>Vd&a< z+QyHhuYlb7(AM&xq=$S()0_4>;Ne;U8J`{yX+l^@=`Jxu)}pM-qLWgm+O zw2F?EOOW?`Bo^N$e|P`Rx&U?f=+^cNAfJG|N9~(pUq4dFIEq zK2Pd}eEQ=!UsU@hJ&zYn+>AnA`-!dX&p=*28f#xuK6l(KL4FMKQnerJj}AU#kY~n< zG;X}KA@6m`>s+zf1GzI68;jJJt~0?A$O}J* z=dfJq+nHXu@3NPJN)1Eq_Mz0X5Pv4X z4{EH;a{X%7ry`f z)Re2k<)@>P{!8$q=C5Nm9VzLL;m^pIxAyZgnPxpLxjVMf&WQVDCB^+eg?K z!89I^ks{~}L1(iVkup?4UiL4W$AYv~7;-=4rFf5VG`5!O1>Xx^K6kTg<@6q{yrdh2 zy!&6{WlzVVUic~Svs1BgEjDk@gCF`@Z2zkJ%k+@hU`jbxAuss`*2d}!%I`9Av737_ z=GW^#6@c~hP$$g{s0>z|$`_P?N{13&LdhkYKF$Ld?jvky8I-`Y60 zjeze1KdWg?|FY|9NjD9-Jja>rQ+aN=4*aAmom~@2x;4nxlG4fgBj*x~88aBGufYxX zq~+WCPkkbDAmhpOj6a^atq30j{^*BusGHa+hyLteHs)Uo{sj1O=!hM&$9H|c*adm- zYAjaFe#|{?3_*Sr@?v+qazi3*I0^aGUt@Eg@r!%TJ1+MB7VF1Geut|+twBBr`IMH= z$n9rNVn5G|82LNeO!GJLbS_#Ua!T=MWesQTnYiJ3E5_7}%rtFP59m7REIWAi2Jht( zjCe5~Cvm;t7G0d=KLl<8oFDID-{kLJQ%^#kpN8kn-2UN)TiWF~~m zR|9Ss@2$`5{OAR>0{J}TBYK7%jvdX7=+T_mkmurSJb3O7@2w3?l&G!7dokqGkk_i* zeC{oNkmu*K&Zd8GFI*kCLU3hxFPGSlh<$L?X}Gbz;lZ-V2O#e|XY>3;J`VXRQV-|^AbE0r*bo=JmB(G9pqJz7pdH=N5pO?_#pUxS30}D z5cweF^N{DLk902O!v`Xtfc)6Sh%ZfV`i6TBpNG8g5)PPK2zhapL6)S z)JS6A0|(~6bYr>#@Fn1@UHQ2`3#foR6i;W$T889H92iwL>UoovIc`QRG^X0L+Z75^*<)}a8 zwOU49AMitZNnZ$g<&}6|TgzzD+h+*Gck+FU+^Y~Pn%Wt+!?wD$nyRl6{L**L;lPwXBAKfD{yH?v+cT-ozJkspJ65^~0g zi3j_+mB^PNUxGZW>5bfuRU*$u(3I?n)z`?~eXtPnKFDia>8m-`N%~62ry=jra_jn> zn`Eh12=ankHrA^LeCMrO=Q{%VG02x(`Py}k*qw%a0N-y&_SbRnixqfIUelR4ai8a| zLB0z4X8sVnc`rx*x;>WOJl7=kEC%lfU#fP^ylmS+(p5v=4f&wT>H6E(G4e_b)2k zeGCt1$i(W!s}J&mJFzCfd-NT<|G<(}*T@*Y9QlsY40Pl)HJNgvfzK;45*Y-I{k&i$=@dEN`$XCU#`c?ZI3o7Xs zAc7MK63;7u2 z*2i`Zj)0#A->2zJdF=X1><&O)bl=uudI_rCl7ql{jq#vd@=YT@Bm{+>LvP>AjXn-sgEE0H29;cZ|d9Z z8aH|%uX+uh_gA@&`R(yF)(GTTHSvC7=7g5Gd>ZnjkXLGY6YF-|lX5LWJ`Xt{p}cD# zmXmlXZ_d^D^IFVnI&XICvt?=6)#x}}4Eb{H*87rb$ctWw?*nLlCLZ1AJrT$UA@6g^ z6WSm0%-6^KZqmEkcO3E{ry3m)!0*r5!?$Psj6*Jtx`&egVAb$R#*P4nwqQ zGy56c82A37Ro=v^`x&G<$ix14-<4FwHB$}kN1Jmj4^wsXE) z@ua^talV^@Uco+m=STIhuf=9L{o&a9c1i5uyMuEHa{Y3Elp|{g+UcR#7-8Dc&W9o| zfP4`0ZoEg_nYE2O4k{pDg?w7&WBR#{4pNdn2>H
    wLtmxZ>rKMMIU{SRZ&kh^@_CgT`#jP!a!)bVHSMu|py`k4+y{D*rU>%7NNjvHa-8{L z`KxPG(W!<`H*`wz9%ZyTm}=tZ=$+6RjO&y%|f;r!vE-l@Rj8dKg$TQ!9xl{Mr#%}sM zbl?0hzDwQp4#W+4^E@*j+!MRQkay#| zy7FBX_|o_({h#9bPC;J!?$|kz%3bF~3y>G}BR!Ja^zP48 z~8w{}S^{8RUge;#@$>r}gfOm#+@;@_#_QooezoXB!#g@%HG3eC?^k_K-d_ z2)^iP%-5%zbhEKDyo~B}9?zM0oq}HI6R~l{)Sp{z)7}e^FF?-vo9FPoFu4kT1mCG0 zILp}Qc#_eOq035>HtzxhZjjEAs_iB_OIC{y*UTn{lpm= zxf$BiBz_j4v+@Icm)dQE4@rvLDM9?;yRcifKjagT^LrE;UvJmCN?eygKJ%m4xaYO& zg&Od4;0GlS#H~DMDEeXW)j!5K&3I1l(R0UM;pb!XcTswq^26Y>e-=AWDw5BW#Lfix z0q~C6sY-uT^=H9%9gmGqvFCu7z>k72*7BHjk6rtUy{uQjpOBYPF3*ZdTYAASf-iaz zZgy?!kH_J+Gu&}l0lnql$I5BO2lu&G5c1G+Y@KHOXxH@84!w}iLO!GU8@c=Wo>9m< z|2r0oCRXg8Ldrh_`2yreHNB^qvAO_0@`tVW*3OL>XCdd=;{wlvPe?ZqyI#n{&nM1b zQr;5qJ>bhVKjTmLXIC|lk3v4^lDpRnosfI~CsrR*Ut1rsKL~j* zrx)cYgWl2_&OjK4wyUSVSEED9>W7Xuy7ipi1Nj2vRqj5@MT(>!f!vc8wR;3pSGz}$ zGE75W1G%H=%^G>nwps5H7FBF4L8mw!-}T0OI}g~snHN-6DaL8YGm~t*fi^^^5IXr8 z`2R?F4;##9%G>PHse;ZVbk-7dX1#}9IuYm$WNvLI4`~OXQ+UeeUDXF8@Xq&=1Q^hLX$xG>{# z9aKf97&-{{N!P5k8CgtkcHrF^;IC%_-m{MApN z)BFn{FM-@IzeNB$$lvt;J==y)eE5kgCt@j!{O-qb$O~0&+S<#&> zz1YvZ8T}9P5tZv_d`Aey9z6<<3 zc-Gs~z!;U^DXA)s^&#V1_j#rKeo5hVF}ddvraK1SgWq>p)VMU|%(y|<6VhhOkk8@w z911+RiM83-8ECVN%%nEUEyH|-|LRqu@yq@6oyIBRvl7Uc?!<2_;5}@ayxik^4diwB zjR%f5rVe(zk@TIAhar#PJ<^-_a(~t^2>CSRqble6Y+o$@3CJt)+Yz-YH~I6UIUTfs_YQ9!Pm0<$;t3QXWWoAmxFS z2T~qLc_8J1lm}8CNO>UTfs_YQ9!Pm0<$;t3QXWWoAmxFS2T~qLc_8J1lm}8CNO>UT zfs_YQ9!Pm0<$;t3QXWWoAmxFS2T~qLc_8J1lm}8CNO>UTfs_YQ9!Pm0<$;t3QXWWo zAmxFS2T~qLc_8J1lm}8CNO>UTfs_YQ9!Pm0<$;t3QXWWoAmxFS2T~qLc_8J1lm}8C zNO>UTfs_YQ9!Pm0<$;t3QXWWoAmxFS2T~qLc_8J1lm}8CNO>UTfs_YQ9!Pm0<$;t3 zQXWWoAmxFS2T~qLc_8J1lm}8CNO>UTfs_YQ9!Pm0<$?bndtkw|Fso=A~(2(&rm#7!O7C% zJEWbTQ~ylLbBX5?Eijo=byCa6WX=i==TPx9;`zjx7wa3#4{y_ZTkZlD^mOXm{4eIc z)%$x?>j%FZYMu99+KckumUD&okJ8b9<^2xY{XYc@Y`7Gv$5Vdzf9Hp?zia>dzZ<`u zS1`YC+Yu`vp`(#_LkNcH#9#yu?QGWjWjCb`BA}fqzr!3^DJ! zdVkxVJvVv_?rZ31iF9~(T(^Da_M)qIT;E~tcf6bo((igH{?rHR8=8Ghf%-^CTfkS} z;`cQ)N80Ic$@PV=3}}vSqL$&LJI6NVGh*|j+j0gX+RlrK{3)^dGm$^lvGYnIf0|?G zqeMQ-vGZOcf4XDm$3*^&*g7GRKhv?}XCj~NOkLt;^nHeNR&0IYmS#BHoS>^eT3Lqk zBIoE;8kd$6%s|hpdDUk8+0N{voAKv3b{&&w=fzv_Ik9z3qW-zgEY~}U{CUo1<6wq! zer$b`s4uGuyCRB9GMo#Xn)JM@=t-6axQrB#&xNkjs4;up_ArpHfc3vnRmb|XQ z;O#H)l5$#pj1~S3`N7Lnzf!^ZJ^4lQbL9Vr{Dh42xTefs>$h!=U00BfwM#)#yR1VY z&B=8v-^~Q4pTGgp`8xi7c;+bqw0y5*m={2$m* zv@>FP?&Gcggyp%fxBM~7bDwYdXDz>;X#WqE=Q)7Yk6NDR0+v4u$`7wEa5-;a6Ql4NEk41&yLrMJk-w9}PjrktwbgHlh>*K0QqI|*HOQFuM!eZa_uHx{-EBkpgwX{k}rO} z-Y+UR_k%a%;dyExME}2@`lWtVJWl-}`P_gK`Q+QcOMOR@>ia19QS!E5yq)}5lKvog z@lT~W9mU}x%ky#a)f`Y4$bZ4u!MZwE%U?l$lKQpniRGUmUlLKilKMX;-_xOdKkM~t z@;S0l#AW05dGe*?t7vD9{QMhKKac*GYD&FMJfggp`Y$1$^O*9s-ESgaM?R=x=e6WL zZ&ae2{C@KNQ8^A6>g$v+8R)^9T$f9h0>lFJq6Nt5?DP8kD` zf)ByYX6u|!8~YbH6KwZ<>Z|=YZ|#49`p1*v#FnQZ4GOMn%IIVLNgmB%u4iaxA<52h z@{39QAIL8y@pjzn8E|Eaw|2a;ydAIp4m;BBB~PmUDwAcNs(jJ=l;|a|_1fkP^E`{R za~Aa{sEq?f8s-`XyfE=@|7P}-d0=7 z>2X#n65IVj=x^5U`zURlfMI)zE?+ivW{dE356$QLEG`_}W$ewF_G75UnKR7Ezw^E~+z zm}DGYPQHeLKTiI7^4VWh{Ti0DoV;Le-(lEicS2xdpt<>5N-koTFa0~m^jy+#aw3F*}C&mBA zVMoSi%X^t%%<$M3Gp>rrtNl1{?SF~-{hP*Nu2Yhf?=IXWk9G!W$Bs)sp?+sl zfBc2jKSTRPH{;~L$meD&kKi-cpDe#EvHwPCzbnap76vpK-|~|5&nNFq;tR+ZkhkNZ zZTHo$v7&_Qp&Lxoh2nYW85$-G(7xq8EYC3cqqC}5LH$pXKas?blP~>&Dwb3K>*Tx1 zmy!QI`6==R?U7`M&@G3t{ z-p1#wY;0ZEEbP2ce1@x85T;pxzO+|GQoGo8x4g~bIqFv>wXf>uI>Slh+uw|SoVWAx zX?XBL@~xpA2lJ&|XIlQe#JKX1uTA35C0|G0w%3K^`;+u9B|k{MM6-3SBtM&^e*^h> z@;08!$uA`FcD;I(e&)38JP3V>^O5`1|HW*EM%r0Q%J(7i%Sn7E`IRL85%MRJ_&1SX zP2wLXzm~-Jk$28d^#2FRXOXvl_?Y2yosp#Z;wbsiB>oGu<4LkVMg5~m`m@x}P0Dux zy!dnFHR{MIw&ODSF)p;{>F1LLs^9y75?%(P9Ur=r#)rSsPFbVsPgDO?JRl@->rE=p z1>_5o_?M9{O5$z*9js04zt=&3v;JE_J0-N!&HCDYYI)Mm-PE6;zU_DDj<~X&>M$L{ z1hzcAzQpp}zeRcW(f-t?^}4{ZJk2}p)SpQzPY?NHN&I`s&nEG<`~`Br?z%n#eTfIl z+hJ~ud{>+5_c6^^$!E1IFXxhSsUNmE6G`##ZR)RdsD6;+)hziMIoQWFX8tD73y)LRkZ6Ay`kTeu-@%)4@}=q@r$RFTjSJ(a<&}1#R$mU>a9Kb6mHU+ZBwp?0 zI^#+H%)oPwvX8U8wR4u?FK`Ny?3_=&Fo}=F^M~komh*Dxn|LOlM}7zS0rFGiZzf+o ztadDaC;6NYC-V1^pCcckojURrA5r~o7ORc?%tw{5abnNmi^i0<`}8+jee(8Pw~xH% z^Qu41eCIeW&5<8t^Q-?oPL^H3QvXTV*(@GLz)L(7(a*RsmyL%QPdlHp`k}=BW!oj| zpJ{;llPtgG%gBF&dA#eTs3-Y-WYTw#fCSOCoj&}Y+ej@Vqf zPIoi^*n0I$CDzLWeTg^AA7#E5k`I4F^|Kl1Hs6Wn#C-GrmwY?FlbCN2?OWdZr;PkE z^+#Er`^lg9rP^5_{}6fmoLn*aH;}i_%~g}{BX6IhD{O@RIj&=%|(dJp4JVJh$e2{kDO+M=x z)$b(#Ve;MNd&vJ2`OI0>xA`6eFa5oQ{g>g1n#vXDX~uaD`r>EnU#owvM|n*bpBMCg zn^SdZVw~Rqeeq}U541e1%-}Zi$K?ZVT!rNCCtpdvpL~G)(a$F8N66<*C|`hkx!z2^ zs8MoT8 zn^SP5`o~K<7lIf6O1!M}Bls^~)8U=gBYshCKCUD3N@Neye;b`EBGwNqipp(Ioy#@{8mvY3D|3|9311 zJI+1erQO49$8)LQWcB~3`qs{2@?GS!X{VoddTGagkL458pZu%ZvHPa4kRMr7ew@X6 zhJ4Ms68YqpXusexjn8tH^91#~QfTtL$lcjb_6r3-Tcg|E| zp8Llilb=6ZdD||(BAr? zl)V2=CGs^}XD9hy@^;+2oxHbF^>b;bj(o}8O4#walYCZ{^24$k8}Q-QQApAPyN&7S35RN&LN)} zQ2r?OFDE}nzK;A2;3ZCG*i?1aY!aV=fz;?< zuKL~7&mljX#9skk;xkhoAjD-K3n(LB*`|C6J3uY%EYZ&xF3iqEsyP|k5NC1`CdXh zPg1}9O{zb_acPYD1=P3SU;P&Km)@oNwH)_;ZtXmwd>{R`N;?%vcDCV*4H6Ht1Bv=q zffxS-ss9p|a~Jh%->3Q>1?K^ye}Pk)v_5}?d|49ze(~8?<1e}X(cA8e}w!L`EK%WBVQ&DG~yZ|KS+L&{0#X|k)P*2VxIh$$&Zbx zoqqD)vGzZw{2KZH055(Rq#x|~`JCZ%ofXzMpZX`s=W@Sn`7;ra;^)%OC)$4*`I0Xv zzsLr@iF`Q^0!tX5caiVG6sHiXV)n?}*R{vPi_o`Z8 zKMhfs%!|3r>~tCZ=c38qlX?qyXOr)F4N`w1N(YDPH5YwB*ZauNv?|}Hv@>dW-nihK z)Gs_k_2(!%PJZ?KN>r2oBl*${)gL9Fg#l3f*7LOTC&*t!zVDC{W#kLU7xTfZMe?sA zANnsPddS}g-pluH`0>kx&jn>?B9%T&J2gkOJP{^(Bl%LEn@LK!hRA#Pxlf_e&Zo(b z)vF!bF5dv}!MRHE_xOH5{i0meFQuK|T05sGKTQ5_qD#?{5- ztNx;VKlO{q=Y3Y~6qCP=e914Bm?vLPezHgPZTohRKgRa5e6Qh|*aaV={shM#8xLO~ zKlY!hJjpcEUPZomLH%I+%U;8)j{9#T_4EE+S&t@jI>BG&R^wfn$1DEXc%5zdY^R^?ZsRG(2xyaIfLBouN4; zt(^dP9MNpJ4uRjSJl}!Sq#spq{bLjM8vSf%cu5)SpQp)ppQ?@@V1h4@9}6pQ^=H6) zFg_&r(`TrE;umUQLBV`aQsQc4DACRe_2=aafVixm(=OFGEKXP6KIeZf`H>GAbv*y8 zcm?^DK@D(gr_%7MU*eIO8Z0P`+3l~aE%H+y@j10Ub1=lzq*C~EbZ5@ z-EF`AFY+MsbC`VA z*R?#;Ow⁣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 q0{__8Uq*e*z4Zz{oZCnr<9!12)→ poll=0;抬手仍 TAP + * (位移 < SWIPE_THRESH 65,手势判定为点击)。 */ + meow_input_touch_set(1, 300, 500, 700); + meow_input_touch_move(300 + 30, 500); + assert(meow_input_long_press_poll() == 0); + meow_input_touch_release(); + assert(meow_input_pop(&e) == 1 && e.type == MEOW_EV_TAP); + + /* ⑤ 阈值可配:设 200ms → hold=250ms 也触发;恢复默认。 */ + meow_ui_set_long_press_ms(200); + assert(meow_ui_long_press_ms() == 200); + meow_input_touch_set(1, 180, 260, 250); + assert(meow_input_long_press_poll() == 1); + meow_input_touch_release(); + meow_ui_set_long_press_ms(500); + assert(meow_ui_long_press_ms() == 500); + + meow_ui_set_long_press_cb(NULL); /* 复位,避免影响其它段 */ + meow_input_touch_set(0, 0, 0, 0); /* 复位,避免影响后续绘制 */ + meow_input_drain(); + printf("PASS long-press: trigger/suppress-tap/short-tap/cancel-move/configurable/opt-in\n"); + } + /* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */ const meow_theme *th = meow_theme_get(); meow_ui_clear(th->bg); diff --git a/test_p1p2p3_repro b/test_p1p2p3_repro new file mode 100755 index 0000000000000000000000000000000000000000..591539e959f5e71ea68453b8144e7a5afc277e08 GIT binary patch literal 379192 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 CnQr^(ci84o>SD`V63U%Nqz2_dXueF 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 S=ifeV&%e?O%7 Date: Mon, 31 Aug 2026 05:04:41 +0800 Subject: [PATCH 28/49] =?UTF-8?q?chore:=20=E8=AF=AF=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E7=9A=84=E5=AE=BF=E4=B8=BB=E6=B5=8B=E8=AF=95=E4=BA=8C=E8=BF=9B?= =?UTF-8?q?=E5=88=B6=20test=5Fp1p2p3=5Frepro=20=E7=A7=BB=E5=87=BA=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA=20+=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_p1p2p3_repro | Bin 379192 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 test_p1p2p3_repro diff --git a/test_p1p2p3_repro b/test_p1p2p3_repro deleted file mode 100755 index 591539e959f5e71ea68453b8144e7a5afc277e08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 379192 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 CnQr^(ci84o>SD`V63U%Nqz2_dXueF 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 S=ifeV&%e?O%7 Date: Mon, 31 Aug 2026 05:24:43 +0800 Subject: [PATCH 29/49] =?UTF-8?q?feat(sdk):=20=E7=BB=98=E5=88=B6=E6=9C=9F?= =?UTF-8?q?=E9=80=9A=E7=94=A8clip=20+=20view=E9=80=92=E5=BD=92/on=5Fevent?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF=20(agent-195)=20=E2=80=94=20=E2=91=A0?= =?UTF-8?q?=E6=A0=88=E5=BC=8Fclip=20rect(g=5Fclip=5Fstack[16]),=20?= =?UTF-8?q?=E6=89=80=E6=9C=89=E5=86=99=E5=83=8F=E7=B4=A0=E5=8E=9F=E8=AF=AD?= =?UTF-8?q?=E8=BF=87clip,=20meow=5Fwidget=5Fdraw=20push/pop=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6=E7=9F=A9=E5=BD=A2(=E5=90=ABW=5FCUSTOM,=20=E6=A0=B9?= =?UTF-8?q?=E6=B2=BB=E8=B6=8A=E7=95=8C=E8=84=8F=E5=8C=BA),=20IME/=E6=A8=A1?= =?UTF-8?q?=E6=80=81=E9=A1=B6=E5=B1=82=E5=85=A8=E5=B1=8F=E4=B8=8D=E5=8F=97?= =?UTF-8?q?=E5=BD=B1=E5=93=8D=20=E2=91=A1view=20enter/leave=E9=80=92?= =?UTF-8?q?=E5=BD=92children=20+=20meow=5Fview=5Fdispatch=5Fevent(z?= =?UTF-8?q?=E9=99=8D=E5=BA=8F=E5=AD=90>=E7=88=B6=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E5=8D=B3=E6=B6=88=E8=B4=B9);=20hello=20App=E5=B1=82=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=E9=A9=B1=E5=8A=A8=E9=80=80=E5=BD=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a→da4d3a7d; hello→c655ba43; 宿主4套件全PASS(含clip越界断言+view递归断言) 出入: W_SLIDER拇指在max处被裁(既有几何瑕疵可见化,待修); on_draw祖先已进入即画 --- meow_ui_draw.c | 10 +++- meow_ui_gfx.c | 101 +++++++++++++++++++++++++++++++++++++++-- meow_ui_internal.h | 8 ++++ meow_ui_log.c | 24 +++++----- meow_ui_view.c | 41 ++++++++++++++++- meow_ui_view.h | 5 ++ meow_ui_widget.c | 5 ++ test_ime_fix | Bin 304304 -> 304600 bytes test_meow_ui_widgets | Bin 303960 -> 304720 bytes test_meow_ui_widgets.c | 72 +++++++++++++++++++++++++++++ 10 files changed, 247 insertions(+), 19 deletions(-) diff --git a/meow_ui_draw.c b/meow_ui_draw.c index fc25145..7ffcd4e 100644 --- a/meow_ui_draw.c +++ b/meow_ui_draw.c @@ -243,7 +243,15 @@ void meow_draw_view_unregister(struct meow_view *v) } int meow_draw_view_entered(const struct meow_view *v) { - return v ? meow_view_get_state(v) & MEOW_VIEW_STATE_ENTERED : 0; + /* 子>父:view 或其任意祖先已进入即视为「可绘制」——配合 view_enter/leave 递归 + * (meow_ui_view.c §A.4),父 on_enter 重绘时子视图(尚未单独进入)也要经 on_draw 画出; + * 且子随父进出("子视图随父进出"),祖先已进入 ⇒ 子树已进入,可绘制。 */ + const struct meow_view *cur = v; + while (cur) { + if (meow_view_get_state(cur) & MEOW_VIEW_STATE_ENTERED) return 1; + cur = meow_view_parent(cur); + } + return 0; } void meow_draw_view_set_z(struct meow_view *v, int z) { diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index e2dd313..bc4c8ca 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -62,6 +62,48 @@ static int g_dirty[/*MUI_DIRTY_MAX*/12][4]; /* [x,y,w,h] */ static int g_ndirty = 0; static int g_dirty_full = 1; +/* ---- 绘制期裁剪(clip rect,§3;默认全屏)---- + * 栈式嵌套:push 与当前 clip 求交后压栈,pop 恢复。写像素原语(put_px / put_px_blend / + * fill_rect / fill_rect_alpha / round / circle / ring / blit_icon / blit_rgba / text) + * 统一过 g_clip;顶层(g_clip_n==0)= 全屏(g_xres×g_yres),故 IME/模态的全屏 scrim + * 直绘不受裁剪。 */ +#define MUI_CLIP_MAX 16 +static int g_clip_stack[MUI_CLIP_MAX][4]; /* [x0,y0,x1,y1](闭开区间) */ +static int g_clip_n = 0; + +static void clip_cur(int out[4]) { /* 当前 clip 边界(无 clip = 全屏) */ + if (g_clip_n > 0) { + out[0] = g_clip_stack[g_clip_n - 1][0]; out[1] = g_clip_stack[g_clip_n - 1][1]; + out[2] = g_clip_stack[g_clip_n - 1][2]; out[3] = g_clip_stack[g_clip_n - 1][3]; + } else { out[0] = 0; out[1] = 0; out[2] = g_xres; out[3] = g_yres; } +} +static int clip_contains(int x, int y) +{ + if (g_clip_n == 0) return x >= 0 && x < g_xres && y >= 0 && y < g_yres; + return x >= g_clip_stack[g_clip_n - 1][0] && x < g_clip_stack[g_clip_n - 1][2] && + y >= g_clip_stack[g_clip_n - 1][1] && y < g_clip_stack[g_clip_n - 1][3]; +} + +void meow_gfx_clip_push(int x, int y, int w, int h) +{ + if (!g_init_ok || w <= 0 || h <= 0) return; + int c[4]; clip_cur(c); + int ix0 = x > c[0] ? x : c[0]; + int iy0 = y > c[1] ? y : c[1]; + int ix1 = (x + w) < c[2] ? (x + w) : c[2]; + int iy1 = (y + h) < c[3] ? (y + h) : c[3]; + if (ix1 < ix0) ix1 = ix0; + if (iy1 < iy0) iy1 = iy0; + if (g_clip_n >= MUI_CLIP_MAX) return; /* 栈满:忽略本次 push(保持当前 clip) */ + g_clip_stack[g_clip_n][0] = ix0; g_clip_stack[g_clip_n][1] = iy0; + g_clip_stack[g_clip_n][2] = ix1; g_clip_stack[g_clip_n][3] = iy1; + g_clip_n++; +} +void meow_gfx_clip_pop(void) +{ + if (g_clip_n > 0) g_clip_n--; +} + static char *g_dump_path = NULL; /* ---- 状态栏「上收出屏只留提示线」(collapsed,基座)---- @@ -158,6 +200,17 @@ static void dirty_add(int x, int y, int w, int h) if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; + /* 绘制期裁剪:脏区也不越 clip(顶层 clip=全屏 → 无操作)。 */ + if (g_clip_n > 0) { + int *c = g_clip_stack[g_clip_n - 1]; + int x1 = x + w, y1 = y + h; + if (x < c[0]) x = c[0]; + if (y < c[1]) y = c[1]; + if (x1 > c[2]) x1 = c[2]; + if (y1 > c[3]) y1 = c[3]; + if (x1 <= x || y1 <= y) return; + w = x1 - x; h = y1 - y; + } for (int i = 0; i < g_ndirty; i++) { if (rect_hit(g_dirty[i][0], g_dirty[i][1], g_dirty[i][2], g_dirty[i][3], x, y, w, h)) { int nx1 = g_dirty[i][0] < x ? g_dirty[i][0] : x; @@ -237,6 +290,7 @@ void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy) for (int dx = 0; dx < t_w; dx++) { int tx = x0 + dx; if (tx < 0 || tx >= g_xres) continue; + if (!clip_contains(tx, ty)) continue; int sx = minx + (dx * ink_w) / t_w; int a = src[sy * SB_ICON_SIZE + sx]; if (a < 8) continue; /* 透明像素跳过 */ @@ -265,7 +319,7 @@ static uint16_t px_read_rgb565(int x, int y) static void put_px(int x, int y, uint32_t color) { - if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; + if (!clip_contains(x, y)) return; if (g_bpp == 16) { px_write_rgb565(x, y, rgb565(color)); } else { @@ -276,7 +330,7 @@ static void put_px(int x, int y, uint32_t color) static void put_px_blend(int x, int y, uint32_t color, int alpha) { - if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; + if (!clip_contains(x, y)) return; uint16_t fg = rgb565(color); if (g_bpp == 16) { uint16_t bg = px_read_rgb565(x, y); @@ -300,6 +354,10 @@ static void put_px_blend(int x, int y, uint32_t color, int alpha) void meow_ui_clear(uint32_t color) { if (!g_init_ok) return; + if (g_clip_n > 0) { /* 绘制期裁剪:清当前 clip 区(顶层 clip=全屏 → 走快速整刷) */ + meow_ui_fill_rect(0, 0, g_xres, g_yres, color); + return; + } /* 快速整刷(16bit 用半字填充,可 memset 当色值由 32bit 拼接掩码) */ if (g_bpp == 16) { uint16_t c16 = rgb565(color); @@ -327,6 +385,16 @@ void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color) if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; + if (g_clip_n > 0) { /* 绘制期裁剪:与当前 clip 求交(顶层 clip=全屏 → 无操作) */ + int *c = g_clip_stack[g_clip_n - 1]; + int x1 = x + w, y1 = y + h; + if (x < c[0]) x = c[0]; + if (y < c[1]) y = c[1]; + if (x1 > c[2]) x1 = c[2]; + if (y1 > c[3]) y1 = c[3]; + if (x1 <= x || y1 <= y) return; + w = x1 - x; h = y1 - y; + } if (g_bpp == 16) { uint16_t c16 = rgb565(color); for (int ry = y; ry < y + h; ry++) { @@ -534,6 +602,16 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; + if (g_clip_n > 0) { /* 绘制期裁剪:与当前 clip 求交(顶层 clip=全屏 → 无操作) */ + int *c = g_clip_stack[g_clip_n - 1]; + int x1 = x + w, y1 = y + h; + if (x < c[0]) x = c[0]; + if (y < c[1]) y = c[1]; + if (x1 > c[2]) x1 = c[2]; + if (y1 > c[3]) y1 = c[3]; + if (x1 <= x || y1 <= y) return; + w = x1 - x; h = y1 - y; + } for (int ry = y; ry < y + h; ry++) for (int rx = x; rx < x + w; rx++) put_px_blend(rx, ry, color, alpha); dirty_add(x, y, w, h); @@ -677,13 +755,28 @@ static void text_draw(int x, int y, const char *s, int size, uint32_t color) { if (!g_init_ok || !s) return; if (g_mfont_ready && g_bpp == 16) { - meow_font_drawtext((uint16_t *)g_fb, x, y, s, rgb565(color), size); int w = meow_font_width_text(s, size); int hh = meow_font_line_height(size); + if (g_clip_n > 0) { + int *c = g_clip_stack[g_clip_n - 1]; + if (x + w <= c[0] || x >= c[2] || y + hh <= c[1] || y >= c[3]) return; /* 与 clip 无交 */ + if (!(x >= c[0] && x + w <= c[2] && y >= c[1] && y + hh <= c[3])) { + /* 部分相交:用「偏移 fb 子矩形 + 临时 fb 几何」做裁剪(meow_font 只按全屏剪)。 + * 把 fb 基址指向 clip 左上角、fb_w/h 设为 clip 尺寸、原点平移到 clip 坐标, + * 这样 meow_font 的逐字形裁剪天然落在 clip 内;画完恢复全屏几何。 */ + uint16_t *fb = (uint16_t *)(g_fb + (size_t)c[1] * g_line + (size_t)c[0] * 2); + meow_font_set_fb((unsigned)(c[2] - c[0]), (unsigned)(c[3] - c[1]), (unsigned)g_line); + meow_font_drawtext(fb, x - c[0], y - c[1], s, rgb565(color), size); + meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line); + dirty_add(x, y, w, hh); + return; + } + } + meow_font_drawtext((uint16_t *)g_fb, x, y, s, rgb565(color), size); dirty_add(x, y, w, hh); } else { int sc = fallback_scale(size); - draw_text5x7(x, y, s, sc, color); + draw_text5x7(x, y, s, sc, color); /* 走 put_px,自动过 clip */ dirty_add(x, y, (int)strlen(s) * 6 * sc, 7 * sc); } } diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 2dac50f..2d22645 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -102,6 +102,14 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h); * 解除后由调用方一次 flush 原子上屏,避免覆盖层与 App on_tick 重绘同帧两刷频闪。 */ void meow_gfx_flush_hold(int on); +/* 绘制期裁剪(MEOW_APP_SDK.md §3,2026-08-31):当前 clip 矩形内才写像素,默认全屏。 + * meow_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM) + * 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。 + * 顶层 clip=全屏,故 IME/模态(snapshot+整帧路径)经 gfx 原语直绘全屏 scrim 时不受裁剪; + * meow_gfx_flush_hold 语义不依赖 clip(flush 只在解除后原子上屏)。 */ +void meow_gfx_clip_push(int x, int y, int w, int h); +void meow_gfx_clip_pop(void); + /* 标记整帧脏(launcher 手动脏模型桥接:自绘 raw 像素后标全屏,present 走 SDK)。 */ void meow_gfx_mark_full(void); diff --git a/meow_ui_log.c b/meow_ui_log.c index 39342c5..d0981c4 100644 --- a/meow_ui_log.c +++ b/meow_ui_log.c @@ -5,9 +5,9 @@ * fopen/fprintf),重复、易错、无分离/级别。本文件实现 SDK 统一 meow_log(): * * - app 一行调用 meow_log(MEOW_LOG_INFO, "start")(见 meow_ui.h); - * - SDK 启动时读 env:MEOW_LOG_DIR(日志目录)、MEOW_LOG_APP(app_id,无则 "app")、 - * MEOW_LOG_LEVEL(本 app 级别,无则默认 INFO)→ 写 - * `/apps//logs/<启动时间>_[._].log`(目录自动 mkdir -p), + * - SDK 启动时读 env:MEOW_LOG_DIR(日志目录,launcher 已注入到 logs/,含 app 分层)、 + * MEOW_LOG_APP(app_id,无则 "app")、MEOW_LOG_LEVEL(本 app 级别,无则默认 INFO)→ 写 + * `/<启动时间>_[._].log`(目录自动 mkdir -p), * 按 app 分目录 + 每次启动新文件(不同启动=不同文件,同一次启动内 O_APPEND 追加), * 级别过滤(lvl < level 丢弃); * - 格式:` [] message\n`(meow_ui_time_ms() 取毫秒); @@ -57,8 +57,8 @@ static char g_app_id[64] = "app"; /* env MEOW_LOG_APP,无则 "app" */ static char g_log_dir[256] = ""; /* env MEOW_LOG_DIR,无/空则降级 stdio */ static meow_log_level g_level = MEOW_LOG_INFO; /* 默认 INFO */ -/* M2.52:日志文件为 `/apps//logs/_[._].log`。 - * g_log_base 存不含序号后缀的路径前缀 `/apps//logs/_`; +/* M2.52:日志文件为 `/_[._].log`;M2.x 简化后 MEOW_LOG_DIR 已含 app 分层 + * (launcher 注入 logs/)。g_log_base 存不含序号后缀的路径前缀 `/_`; * g_log_seq 为轮转序号(0=首个文件无后缀;>0 追加 `_`)。两者均在 log_init() * (pthread_once 那次)里生成一次。 */ static char g_log_base[384] = ""; @@ -93,7 +93,7 @@ static void log_init(void) s = getenv("MEOW_LOG_DIR"); if (s && s[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", s); - /* M2.52:构建完整前缀 `/apps//logs/_`。 + /* M2.52:构建完整前缀 `/_`(M2.x 简化:MEOW_LOG_DIR 已含 app 分层)。 * ts 用 localtime 的 %Y%m%d_%H%M%S(启动时刻);尾拼 _ 防同秒重复启动撞名, * 从而每次启动=新文件,同一次启动内 O_APPEND 追加。若 log_dir 为空(无卡) * 则跳过(log_open 降级 stderr)。 */ @@ -103,8 +103,8 @@ static void log_init(void) struct tm tmnow; localtime_r(&now, &tmnow); strftime(ts, sizeof ts, "%Y%m%d_%H%M%S", &tmnow); - snprintf(g_log_base, sizeof g_log_base, "%s/apps/%s/logs/%s_%d", - g_log_dir, g_app_id, ts, (int)getpid()); + snprintf(g_log_base, sizeof g_log_base, "%s/%s_%d", + g_log_dir, ts, (int)getpid()); log_rebuild_file(); } @@ -138,7 +138,7 @@ static int log_mkdir_p(const char *path) return 0; } -/* M2.52:对 `/apps//logs` 增量 mkdir -p,并打开 g_log_file +/* M2.52:对 ``(M2.x 简化后已含 app 分层)增量 mkdir -p,并打开 g_log_file * (O_APPEND|O_CREAT;同一次启动内追加,不同启动=不同文件)。失败置 g_log_fallback。 * 首次由 meow_log 经 g_log_open_tried 守卫调用;轮转后由 meow_log 再次调用(目录已存在, * mkdir_p 幂等)。仅在持有 g_log_mu 时调用(g_log_base 已构建)。 */ @@ -146,7 +146,7 @@ static void log_open(void) { char logs[512]; if (!g_log_dir[0] || !g_log_file[0]) { g_log_fallback = 1; return; } - if (snprintf(logs, sizeof logs, "%s/apps/%s/logs", g_log_dir, g_app_id) >= (int)sizeof logs) { + if (snprintf(logs, sizeof logs, "%s", g_log_dir) >= (int)sizeof logs) { g_log_fallback = 1; return; } if (log_mkdir_p(logs) != 0) { g_log_fallback = 1; return; } @@ -165,7 +165,7 @@ static int log_spill_cmp(const void *a, const void *b) return 0; } -/* 每 App 总量清理:扫描 `/apps//logs/` 下所有 .log(跳过当前正写的 +/* 每 App 总量清理:扫描 `/`(M2.x 简化后已含 app 分层)下所有 .log(跳过当前正写的 * g_log_file),累计大小,超过 MEOW_LOG_TOTAL_MAX 则按 mtime 从最旧删到 ≤ 阈值。 * 只规整本 App 目录(进程隔离),节流调用(见 meow_log)。仅在持有 g_log_mu 时调用。 */ static void log_cleanup_old(void) @@ -178,7 +178,7 @@ static void log_cleanup_old(void) long total = 0; if (!g_log_dir[0] || !g_log_file[0]) return; - if (snprintf(logs, sizeof logs, "%s/apps/%s/logs", g_log_dir, g_app_id) + if (snprintf(logs, sizeof logs, "%s", g_log_dir) >= (int)sizeof logs) return; d = opendir(logs); if (!d) return; diff --git a/meow_ui_view.c b/meow_ui_view.c index 9ab349d..e1dc8e1 100644 --- a/meow_ui_view.c +++ b/meow_ui_view.c @@ -40,17 +40,24 @@ static meow_view *view_map_get(int id) return (id >= 0 && id < MEOW_VIEW_BY_ID_MAX) ? g_view_by_id[id] : NULL; } -/* 进入一个 view(置 ENTERED + on_enter)。 */ +/* 进入一个 view(置 ENTERED + on_enter),**递归 children**(子视图随父进出,§A.4)。 + * 进入顺序:父 on_enter 先(记录顺序 home.enter→demo.enter),随后子视图依递归逐个进入; + * 子视图的 on_draw 经 meow_draw_view_entered(含祖先已进入判定)保证父 on_enter 重绘时可见。 */ static void view_enter(meow_view *v) { if (!v || (v->state & MEOW_VIEW_STATE_ENTERED)) return; v->state |= MEOW_VIEW_STATE_ENTERED; run_fn(v->hooks.on_enter, v); + for (int i = 0; i < v->nchildren; i++) + view_enter(v->children[i]); } -/* 离开一个 view(清 ENTERED + on_leave;默认不销毁子 view/widget——弹回零重建)。 */ +/* 离开一个 view(清 ENTERED + on_leave;默认不销毁子 view/widget——弹回零重建), + * **递归 children**:先子后父(demo.leave→home.leave)。 */ static void view_leave(meow_view *v) { if (!v || !(v->state & MEOW_VIEW_STATE_ENTERED)) return; + for (int i = 0; i < v->nchildren; i++) + view_leave(v->children[i]); v->state &= ~MEOW_VIEW_STATE_ENTERED; run_fn(v->hooks.on_leave, v); } @@ -175,6 +182,36 @@ meow_view *meow_view_sub_pop(meow_view *v) return NULL; /* 预留 */ } +/* ============================================================ */ +/* 事件路由(§A.7:每个 view 的 on_event 先把事件发给后代(按 z 降序),命中即消费, + * 再落本 view 的控件)。SDK 提供递归树走;叶子 on_event 负责本 view 的 widget 命中。 */ +/* ============================================================ */ +static int view_dispatch_event(meow_view *v, int ev, int a, int b) +{ + if (!v || !(v->state & MEOW_VIEW_STATE_ENTERED)) return 0; /* 仅已进入的视图参与 */ + if (v->nchildren > 0) { + meow_view *kids[MEOW_VIEW_MAX_CHILD]; + int nn = v->nchildren; + for (int i = 0; i < nn; i++) kids[i] = v->children[i]; + /* 按 z 降序(同 z 保持挂载序稳定);z 同值时树序,靠后挂的在先。 */ + for (int i = 1; i < nn; i++) { + meow_view *key = kids[i]; + int j = i - 1; + while (j >= 0 && meow_view_z(kids[j]) < meow_view_z(key)) { kids[j + 1] = kids[j]; j--; } + kids[j + 1] = key; + } + for (int i = 0; i < nn; i++) + if (view_dispatch_event(kids[i], ev, a, b)) return 1; /* 子命中即消费 */ + } + if (v->hooks.on_event && v->hooks.on_event(v, ev, a, b, v->ctx)) return 1; /* 落到本 view 事件 */ + return 0; +} + +int meow_view_dispatch_event(int ev, int a, int b) +{ + return view_dispatch_event(g_top_view, ev, a, b); +} + /* ============================================================ */ /* 坐标 / 查询 */ /* ============================================================ */ diff --git a/meow_ui_view.h b/meow_ui_view.h index 0b221b9..729d703 100644 --- a/meow_ui_view.h +++ b/meow_ui_view.h @@ -80,6 +80,11 @@ void meow_view_page_push(int id); /* activity_push + 旧top on_leav void meow_view_page_pop(void); /* 旧top on_leave + activity_pop + 新top on_enter */ void meow_view_page_replace(int id); /* 旧top on_leave + 新页 on_enter(目标页须已 create) */ +/* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费, + * 再落本 view 的 on_event(其内做该 view 的 widget 命中)。返回 1=已消费(§A.7)。 + * 未接线于 meow_ui_run(App 可在 on_tap/on_swipe 等回调里改用它实现 SDK 原生路由)。 */ +int meow_view_dispatch_event(int ev, int a, int b); + /* ---- 页内子导航(预留扩展位 a;本批为空实现/桩,仅在 ACTIVITY view 内维护子栈)---- */ int meow_view_sub_push(meow_view *v, meow_view *sub); /* 预留;返回 0(未实现于本批) */ meow_view *meow_view_sub_pop(meow_view *v); /* 预留;返回 NULL(未实现于本批) */ diff --git a/meow_ui_widget.c b/meow_ui_widget.c index e3fc97b..4f8ef37 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -1204,6 +1204,10 @@ int meow_switch_get(meow_widget *w) { return w && w->type == W_SWITCH ? w->on : void meow_widget_draw(meow_widget *w) { if (!w) return; + /* 绘制期裁剪(§3):把绘制 clip 设为该控件矩形,画完恢复——任何控件(含 W_CUSTOM + * 自定义画布)不可能画出自身边界,从根上杜绝越界脏区。可嵌套(W_LAYOUT/W_CARD 递归 + * 子控件会再 push 子控件矩形);顶层 clip=全屏,故 IME/模态直绘不受影响。 */ + meow_gfx_clip_push(w->x, w->y, w->w, w->h); const meow_theme *th = meow_theme_get(); int disabled = widget_disabled(w); @@ -1616,4 +1620,5 @@ void meow_widget_draw(meow_widget *w) default: break; } + meow_gfx_clip_pop(); /* 恢复进入前的 clip(嵌套:回到父控件矩形/全屏) */ } diff --git a/test_ime_fix b/test_ime_fix index 87d348af09d93885fe41d11ad9e25b68d2845eea..14f793e155d26ea9cf7ba23b5b6345595900471c 100755 GIT binary patch delta 72692 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 delta 68861 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 delta 82621 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 I&^hP-0sM?z4gdfE diff --git a/test_meow_ui_widgets.c b/test_meow_ui_widgets.c index 266edb8..6dae3d2 100644 --- a/test_meow_ui_widgets.c +++ b/test_meow_ui_widgets.c @@ -14,6 +14,7 @@ */ #include "meow_ui.h" #include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */ +#include "meow_ui_view.h" /* 任务② view 递归 + on_event 路由断言 */ #include #include #include @@ -95,6 +96,31 @@ static void free_all(void) /* §6.3 通用长按测试回调(开闸长按判定用;事件本身经 meow_input_pop 断言)。 */ static void lp_recv(int x, int y) { (void)x; (void)y; } +/* ---- 任务① 绘制期 clip:W_CUSTOM 越界绘制被裁掉(render 回调经 flag 切换越界)---- + * 控件几何硬编码为 (40,40,80,40)(与下方创建一致):meow_widget 是对外不透明类型, + * 测试经 meow_widget_custom_create(40,40,80,40,...) 建在同址,回调按常量绘制。 */ +static int g_custom_oob = 1; /* 1=画越界实心块+越界文本;0=只画控件内 */ +static void clip_oob_render(meow_widget *w, const meow_theme *th, void *ctx) +{ + (void)w; (void)th; (void)ctx; + meow_ui_fill_rect(45, 45, 20, 20, 0xFF0000); /* 控件 (40,40,80,40) 内:必画红块 */ + if (g_custom_oob) { + /* 越界(右/下,出控件矩形 [40..120)×[40..80)):应被 widget clip 裁掉 */ + meow_ui_fill_rect(140, 40, 60, 40, 0xFF0000); + meow_ui_text(160, 100, "OOB", 20, 0xFF0000); + } +} + +/* ---- 任务② view 递归 enter/leave + on_event 子>父:trace 顺序 + 命中计数 ---- */ +static int g_vtrace[16]; static int g_vtrace_n = 0; +static void vtrace_enter(meow_view *v, void *ctx) { (void)ctx; g_vtrace[g_vtrace_n++] = v->id; } +static void vtrace_leave(meow_view *v, void *ctx) { (void)ctx; g_vtrace[g_vtrace_n++] = -v->id; } +static int g_ev_child_hits = 0, g_ev_parent_hits = 0; +static int vev_child(meow_view *v, int ev, int a, int b, void *ctx) +{ (void)v; (void)ctx; if (ev == 7 && a == 10 && b == 10) { g_ev_child_hits++; return 1; } return 0; } +static int vev_parent(meow_view *v, int ev, int a, int b, void *ctx) +{ (void)v; (void)ctx; if (ev == 7) { g_ev_parent_hits++; return 1; } return 0; } + int main(void) { setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1); @@ -361,6 +387,52 @@ int main(void) printf("PASS long-press: trigger/suppress-tap/short-tap/cancel-move/configurable/opt-in\n"); } + /* ---------- 任务① 绘制期 clip:W_CUSTOM 越界绘制被裁掉 ---------- */ + { + const meow_theme *thc = meow_theme_get(); + int bg_r, bg_g, bg_b; UNPK(thc->bg, bg_r, bg_g, bg_b); + meow_widget *cw = W(meow_widget_custom_create(40, 40, 80, 40, clip_oob_render, NULL, NULL)); + /* a) clip 生效:经 meow_widget_draw(push 控件矩形)→ 越界块/文本被裁掉 */ + g_custom_oob = 1; + meow_ui_clear(thc->bg); + meow_widget_draw(cw); + assert(region_has(45, 45, 20, 20, 0xFF, 0x00, 0x00) > 30); /* 控件内红块画出 */ + assert(px_near(40 + 80 + 25, 40 + 10, bg_r, bg_g, bg_b)); /* 越界实心块被裁 → bg */ + assert(px_near(40 + 80 + 45, 40 + 65, bg_r, bg_g, bg_b)); /* 越界文本被裁 → bg */ + /* b) 对照:无 clip 直绘同尺寸块会画出(证明是 clip 在裁) */ + meow_ui_clear(thc->bg); + meow_ui_fill_rect(40 + 80 + 20, 40, 60, 40, 0x00FF00); + assert(region_has(40 + 80 + 20, 40, 60, 40, 0x00, 0xFF, 0x00) > 100); + printf("PASS clip: W_CUSTOM out-of-bounds fill+text clipped by widget clip\n"); + } + + /* ---------- 任务② view 递归 enter/leave + on_event 子>父命中 ---------- */ + { + static const meow_view_hooks ph = { .on_enter = vtrace_enter, .on_leave = vtrace_leave, + .on_event = vev_parent }; + static const meow_view_hooks ch = { .on_enter = vtrace_enter, .on_leave = vtrace_leave, + .on_event = vev_child }; + /* 页 id 须 < MEOW_VIEW_BY_ID_MAX(32) 才能进 g_view_by_id(page_push 查找) */ + meow_view *pv = meow_view_create(3, MEOW_VIEW_ACTIVITY, &ph, NULL); + meow_view *cv = meow_view_create(4, MEOW_VIEW_CONTAINER, &ch, NULL); + assert(meow_view_add_child(pv, cv) == 0); + g_vtrace_n = 0; g_ev_child_hits = 0; g_ev_parent_hits = 0; + meow_view_page_push(3); /* 进入 pv → 递归进 cv:pv.on_enter→cv.on_enter */ + assert(g_vtrace_n == 2 && g_vtrace[0] == 3 && g_vtrace[1] == 4); + /* 命中 (10,10):先发子(按 z 降序),子命中即消费,父不再收 */ + assert(meow_view_dispatch_event(7, 10, 10) == 1); + assert(g_ev_child_hits == 1 && g_ev_parent_hits == 0); + /* 命中 (20,20):子不命中 → 落父 on_event */ + assert(meow_view_dispatch_event(7, 20, 20) == 1); + assert(g_ev_child_hits == 1 && g_ev_parent_hits == 1); + /* 离开 pv → 递归离开:cv.on_leave(-4) → pv.on_leave(-3) */ + g_vtrace_n = 0; + meow_view_page_pop(); + assert(g_vtrace_n == 2 && g_vtrace[0] == -4 && g_vtrace[1] == -3); + meow_view_free(pv); /* 递归释放 cv */ + printf("PASS view: recursive enter/leave order + on_event child>parent hit\n"); + } + /* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */ const meow_theme *th = meow_theme_get(); meow_ui_clear(th->bg); From 3cc4e35794cb0e6b1c2244d6135b3e3f1059436e Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 05:30:04 +0800 Subject: [PATCH 30/49] =?UTF-8?q?fix(sdk):=20App=20=E9=80=80=E5=87=BA?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=85=9C=E5=BA=95=20=E2=80=94=20meow=5Fui=5F?= =?UTF-8?q?init=20=E5=AE=89=E8=A3=85=20SIGTERM/SIGINT=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=99=A8(log=5Fsig=5Fexit:=20=E7=9B=B4=E5=86=99=20exit:sigterm?= =?UTF-8?q?=20=E7=BB=95=E8=BF=87=E7=BC=93=E5=86=B2+exit(0))=EF=BC=9B?= =?UTF-8?q?=E4=BF=AE=20watchdog=20=E8=BF=94=E5=9B=9E=E9=94=AE=E6=9D=80=20A?= =?UTF-8?q?pp=20=E6=97=B6=E6=97=A5=E5=BF=97=E5=8F=AA=E6=9C=89=20start=20?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=20exit=20=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=9BApp=20=E8=87=AA=E8=A3=85=E5=A4=84=E7=90=86=E5=99=A8(he?= =?UTF-8?q?llo=20BYE)=E8=A6=86=E7=9B=96=E4=B8=8D=E5=8F=97=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=20+=20meow=5Fui.h=20=E6=97=A7=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=B3=A8=E9=87=8A=E6=9B=B4=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a→049fa9db; hello→d0ac55cf; files→3b3bea05; launcher→4f223701; 宿主 widgets PASS --- meow_ui.h | 2 +- meow_ui_gfx.c | 1 + meow_ui_internal.h | 3 +++ meow_ui_log.c | 25 +++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/meow_ui.h b/meow_ui.h index d2a2f7b..0a21284 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -692,7 +692,7 @@ void meow_sheet_free(meow_sheet *s); /* §9 应用日志(统一日志 API,M2.51 最小可用版) */ /* ============================================================ */ /* app 只需一行 meow_log();SDK 管「写哪/怎么写/过滤/降级/上限」: - * - 路径:/apps//logs/<启动时间>_.log + * - 路径:/<启动时间>_.log(MEOW_LOG_DIR 已含包名分层 logs/<包名>) * (目录不存在 mkdir -p,按 app 分目录),每次启动=新文件,同一次启动内 O_APPEND 追加; * - 级别过滤:lvl < meow_log_get_level() 丢弃;默认 MEOW_LOG_INFO; * - 格式: [] message\n; diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index bc4c8ca..a25a41f 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -1135,6 +1135,7 @@ static void meow_gfx_load_multifont(void) int meow_ui_init(const meow_ui_opts *opts) { if (g_init_ok) return 0; + meow_log_install_exit_handler(); /* M2.78:SIGTERM/SIGINT 兜底写 exit: 日志 */ int fw = 0, fh = 0; if (opts) { fw = opts->width; fh = opts->height; } diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 2d22645..241b396 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -102,6 +102,9 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h); * 解除后由调用方一次 flush 原子上屏,避免覆盖层与 App on_tick 重绘同帧两刷频闪。 */ void meow_gfx_flush_hold(int on); +/* M2.78:安装 SIGTERM/SIGINT 退出日志兜底处理器(meow_ui_log.c;meow_ui_init 调用)。 */ +void meow_log_install_exit_handler(void); + /* 绘制期裁剪(MEOW_APP_SDK.md §3,2026-08-31):当前 clip 矩形内才写像素,默认全屏。 * meow_widget_draw(w) 画任何控件前 push 该控件矩形、画完 pop——任何控件(含 W_CUSTOM) * 画不出自身边界,根治自定义画布越界脏区。可嵌套(入栈/出栈,push 与当前 clip 求交)。 diff --git a/meow_ui_log.c b/meow_ui_log.c index d0981c4..73b4fc0 100644 --- a/meow_ui_log.c +++ b/meow_ui_log.c @@ -35,6 +35,7 @@ #include #include #include +#include #include /* 单行消息上限(超长截断) */ @@ -70,6 +71,8 @@ static int g_log_fallback = 0; /* 1=无卡/写失败,降级 stderr */ static int g_log_open_tried = 0; /* 是否已尝试开首个文件(只试一次,失败则降级) */ static unsigned long g_write_count = 0; /* 累计写入次数(周期总量清理节流) */ +static void log_open(void); /* 前向声明(log_sig_exit 在其定义之前使用) */ + /* 由 g_log_base + g_log_seq 重建当前文件路径(seq=0 无后缀,>0 追加 _)。 */ static void log_rebuild_file(void) { @@ -118,6 +121,28 @@ static void log_init(void) } } +/* 退出日志兜底(M2.78):App 被 watchdog 返回键 SIGTERM 时走默认终止,跳过正常 + * 路径的 meow_log("exit"),日志只剩 start。SDK 在 meow_ui_init 安装本处理器: + * SIGTERM/SIGINT → 直接 write 一条 exit:(绕过 stdio 缓冲)→ exit(0)(干净码)。 + * App 自装处理器(如 hello 画 BYE)会覆盖本处理器,其 exit(0) 路径不受影响。 */ +static void log_sig_exit(int sig) +{ + if (!g_log_open_tried) { g_log_open_tried = 1; log_open(); } + if (g_log_fd >= 0) { + char line[128]; + int n = snprintf(line, sizeof line, "%llu [%s] INFO exit:%s\n", + (unsigned long long)meow_ui_time_ms(), g_app_id, + sig == SIGTERM ? "sigterm" : "sigint"); + if (n > 0) { ssize_t w = write(g_log_fd, line, (size_t)n); (void)w; } + } + exit(0); +} +void meow_log_install_exit_handler(void) +{ + signal(SIGTERM, log_sig_exit); + signal(SIGINT, log_sig_exit); +} + /* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。 */ static int log_mkdir_p(const char *path) { From 2796f181f72efed02a364b9526061180efc13874 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Mon, 31 Aug 2026 07:53:57 +0800 Subject: [PATCH 31/49] =?UTF-8?q?feat(sdk+launcher):=20SDK=20nav/media?= =?UTF-8?q?=E5=B0=81=E8=A3=85=20+=20=C2=A710.5=20App=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E5=94=A4=E8=B5=B7=E9=80=9A=E7=9F=A5=E4=B8=AD=E5=BF=83=20(agent?= =?UTF-8?q?-206)=20=E2=80=94=20meow=5Fui=5Fnav.c(env=E6=A0=88=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96+report/request=5Fexit)=20+=20meow=5Fui=5Fmed?= =?UTF-8?q?ia.c(=E6=87=92=E8=BF=9E=E6=96=AD=E7=BA=BF=E9=87=8D=E8=BF=9E,=20?= =?UTF-8?q?meta=E4=B8=8A=E6=8A=A5+playctl=E6=8E=A5=E6=94=B6)=20+=20MSG=5FN?= =?UTF-8?q?OTIF=5FOPEN=3D0x28/CLOSE=3D0x29(0x25/26=E5=B7=B2=E8=A2=AB?= =?UTF-8?q?=E5=8D=A0);=20SDK=E9=A1=B6=E9=83=A8=E4=B8=8B=E6=BB=91=E6=89=8B?= =?UTF-8?q?=E5=8A=BF=E2=86=92IPC;=20launcher=20App=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E5=8F=A0=E5=8A=A0=E9=80=9A=E7=9F=A5=E4=B8=AD?= =?UTF-8?q?=E5=BF=83(SIGSTOP=E2=86=92=E8=AF=BB=E5=B8=A7=E2=86=92=E6=8E=A5?= =?UTF-8?q?=E7=AE=A1=E8=BE=93=E5=85=A5=E2=86=92=E6=81=A2=E5=A4=8DSIGCONT,?= =?UTF-8?q?=20=E9=9D=A2=E6=9D=BF=E5=86=85App=E5=B4=A9=E6=BA=83=E5=8D=B3?= =?UTF-8?q?=E5=85=B3);=20hello=E6=8E=A5=E7=BA=BF=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit meow_ui.a→9a8eb7ef; hello→fc299577; launcher→b33da20a; 宿主5套件+新test_notif_open+qemu分发 全过 出入: SDK符号用meow_ui_前缀防与launcher内同名冲突; 叠加会话真机待验; §10.5文档旧号段已过时 --- build_sdk.sh | 2 +- meow_notify_ipc.c | 52 +++++++++ meow_notify_ipc.h | 14 +++ meow_ui.h | 1 + meow_ui_activity.c | 5 + meow_ui_input.c | 5 +- meow_ui_media.c | 253 +++++++++++++++++++++++++++++++++++++++++ meow_ui_media.h | 45 ++++++++ meow_ui_nav.c | 74 ++++++++++++ meow_ui_nav.h | 42 +++++++ meow_ui_sys.c | 17 ++- meow_ui_sys.h | 9 +- test_ime_fix | Bin 304600 -> 229712 bytes test_ime_fix.c | 2 +- test_meow_ui_widgets | Bin 304720 -> 306120 bytes test_meow_ui_widgets.c | 2 +- test_notif_open | Bin 0 -> 150936 bytes test_notif_open.c | 191 +++++++++++++++++++++++++++++++ test_p1p2p3_repro.c | 2 +- test_sb_collapse | Bin 0 -> 150360 bytes test_sb_collapse.c | 6 +- test_sdk_fixes | Bin 0 -> 229912 bytes test_sdk_fixes.c | 2 +- 23 files changed, 705 insertions(+), 19 deletions(-) create mode 100644 meow_ui_media.c create mode 100644 meow_ui_media.h create mode 100644 meow_ui_nav.c create mode 100644 meow_ui_nav.h create mode 100755 test_notif_open create mode 100644 test_notif_open.c create mode 100755 test_sb_collapse create mode 100755 test_sdk_fixes diff --git a/build_sdk.sh b/build_sdk.sh index b7440e4..2150cfc 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_notify_ipc.c b/meow_notify_ipc.c index 6486fed..5445b6a 100644 --- a/meow_notify_ipc.c +++ b/meow_notify_ipc.c @@ -280,3 +280,55 @@ void meow_notify_ipc_update_progress(uint32_t nid, int progress) } close(fd); } + +/* ---- 通用一次性帧(meow_ui_nav / notif_open 复用本传输层) ---- */ +int meow_notify_ipc_send_raw(uint16_t type, const void *payload, uint32_t len, int want_ack) +{ + int fd = conn_path(ipc_sock_path()); + if (fd < 0) return -1; + set_ack_timeout(fd); + if (!send_frame(fd, type, 0, payload, len)) { close(fd); return -1; } + if (want_ack) { + /* 阻塞读一帧应答头(best-effort:读到即确认 launcher 已处理;内容不关心)。 + * 超时(SO_RCVTIMEO)/断开 → read_all 返回 0,视为已发出(不崩)。 */ + uint8_t rhdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, rhdr, sizeof rhdr)) { + /* 读完应答负载(若声明),保持连接语义一致;不校验 magic(best-effort) */ + uint32_t rlen; + memcpy(&rlen, rhdr + 12, 4); + if (rlen > 0 && rlen <= MEOW_IPC_MAX) { + uint8_t pay[256]; + size_t to = rlen < sizeof pay ? rlen : sizeof pay; + (void)read_all(fd, pay, to); + } + } + } + close(fd); + return 0; +} + +void meow_notify_ipc_notif_open(const char *app_id, uint32_t event_seq, + uint16_t origin_x, uint16_t origin_y, + uint16_t hotzone, uint8_t flags) +{ + m_notif_open_t o; + memset(&o, 0, sizeof o); + snprintf(o.app_id, sizeof o.app_id, "%s", + (app_id && app_id[0]) ? app_id : meow_notify_ipc_app_id()); + o.event_seq = event_seq; + o.origin_x = origin_x; + o.origin_y = origin_y; + o.hotzone = hotzone; + o.flags = flags; + (void)meow_notify_ipc_send_raw(MSG_NOTIF_OPEN, &o, (uint32_t)sizeof o, 1); +} + +void meow_notify_ipc_notif_close(const char *app_id, uint32_t reason) +{ + m_notif_close_t o; + memset(&o, 0, sizeof o); + snprintf(o.app_id, sizeof o.app_id, "%s", + (app_id && app_id[0]) ? app_id : meow_notify_ipc_app_id()); + o.reason = reason; + (void)meow_notify_ipc_send_raw(MSG_NOTIF_CLOSE, &o, (uint32_t)sizeof o, 1); +} diff --git a/meow_notify_ipc.h b/meow_notify_ipc.h index f02a5a0..14df264 100644 --- a/meow_notify_ipc.h +++ b/meow_notify_ipc.h @@ -43,6 +43,20 @@ void meow_notify_ipc_dismiss(uint32_t nid); * 不落盘)。best-effort:写失败/无 socket 静默。 */ void meow_notify_ipc_update_progress(uint32_t nid, int progress); +/* 通用一次性请求:发 type+payload 帧,want_ack 时阻塞读一帧应答(忽略其内容,仅确保 + * launcher 已处理完再关连接,防「写后即关」丢帧竞态)。返回 0=已发出(want_ack 含读到应答); + * -1=无 socket/写失败/读失败。best-effort,供 SDK 内部 nav / notif_open 复用。 */ +int meow_notify_ipc_send_raw(uint16_t type, const void *payload, uint32_t len, int want_ack); + +/* 一次性发送 MSG_NOTIF_OPEN(App 前台顶部下滑 → 请求 launcher 展开通知中心叠加会话, + * FRAMEWORK_DESIGN §10.5)。App 只检测+上报、不渲染系统 UI。best-effort:失败静默。 */ +void meow_notify_ipc_notif_open(const char *app_id, uint32_t event_seq, + uint16_t origin_x, uint16_t origin_y, + uint16_t hotzone, uint8_t flags); + +/* 一次性发送 MSG_NOTIF_CLOSE(App 告知结束/退出 → launcher 收尾兜底,可选用途)。best-effort。 */ +void meow_notify_ipc_notif_close(const char *app_id, uint32_t reason); + #ifdef __cplusplus } #endif diff --git a/meow_ui.h b/meow_ui.h index 0a21284..63a82c3 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -218,6 +218,7 @@ int meow_ui_long_press_ms(void); /* 当前阈值(默认 void meow_ui_activity_push(int id); void meow_ui_activity_pop(void); int meow_ui_activity_active(void); /* 当前 active activity id(栈空=0) */ +int meow_ui_activity_depth(void); /* 当前栈深(push/pop 页数;供 M3 导航上报用) */ /* ② App 跨 Activity 控件(SDK 托管,骨架)。SDK 维护全局控件列表: * - 渲染:每帧(有脏区需上屏时,meow_ui_gfx present())在 active activity 内容之上、 diff --git a/meow_ui_activity.c b/meow_ui_activity.c index 205d215..84f29ef 100644 --- a/meow_ui_activity.c +++ b/meow_ui_activity.c @@ -31,3 +31,8 @@ int meow_ui_activity_active(void) { return g_act_n > 0 ? g_act[g_act_n - 1] : 0; } + +int meow_ui_activity_depth(void) +{ + return g_act_n; +} diff --git a/meow_ui_input.c b/meow_ui_input.c index 1629eb7..a931b65 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -436,8 +436,9 @@ void meow_ui_run(const meow_ui_events *ev) } else if (e.type == MEOW_EV_NOTIF_PULL) { if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 - * APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */ - meow_ui_sys_notif_pull(); + * APP on_swipe;路由到系统钩子(App 进程只检测+经 IPC 上报 MSG_NOTIF_OPEN, + * 通知中心由 launcher 渲染,见 FRAMEWORK_DESIGN §10.5)。 */ + meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */ } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, * on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按 diff --git a/meow_ui_media.c b/meow_ui_media.c new file mode 100644 index 0000000..6063642 --- /dev/null +++ b/meow_ui_media.c @@ -0,0 +1,253 @@ +/* meow_ui_media.c — App 侧媒体会话封装(SDK 公共件;见 meow_ui_media.h)。 + * + * 传输:AF_UNIX SOCK_STREAM 到 MEOW_IPC_SOCK(缺省 /usr/data/meow_ipc.sock)。后台线程持有 + * 持久连接:HELLO(app_id, MF_PLAYBACK) 注册为媒体源 + poll 读 launcher 下发 MSG_PLAYCTL(经 + * 该连接 fd 回发,见 launcher meow_media.c send_playctl)。meow_ui_media_meta() 在调用线程 + * 直接经同一连接发 MSG_META(全双工:写 META、读 PLAYCTL 互不干扰)。 + * + * 连接生命周期(best-effort):懒连(首次 meta / 注册回调时 connect+HELLO+ACK); + * 断线(读失败/写失败)→ close+置 -1,下次 meta 重连;launcher 不在 → connect 失败 → 静默。 + * 线程安全:g_fd 读写经互斥;后台线程只在 poll/读失败时改 fd,meta 线程只在 connect 时改 fd。 + */ +#include "meow_ui_media.h" +#include "meow_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static char g_app[MEOW_APP_ID_MAX] = ""; +static int g_app_set = 0; +static int g_fd = -1; +static pthread_t g_thr; +static int g_thr_on = 0; +static volatile int g_quit = 0; +static meow_media_playctl_fn g_cb = NULL; + +/* 后台线程主体(定义在文件后段;on_playctl 懒起时引用)。 */ +static void *media_thread_main(void *arg); + +static const char *sock_path(void) +{ + const char *e = getenv("MEOW_IPC_SOCK"); + return (e && e[0]) ? e : MEOW_IPC_PATH; +} + +static int connect_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} + +/* 写满 n 字节(EINTR 重试);失败返回 0。 */ +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return 0; } + if (w == 0) return 0; + off += (size_t)w; + } + return 1; +} + +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return 0; } + if (r == 0) return 0; + off += (size_t)r; + } + return 1; +} + +/* 组装并写出 16B(LE)帧头 + 负载。 */ +static int send_frame(int fd, uint16_t type, uint32_t req_id, + const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC; + uint16_t ver = MEOW_IPC_VERSION; + memcpy(hdr + 0, &magic, 4); + memcpy(hdr + 4, &ver, 2); + memcpy(hdr + 6, &type, 2); + memcpy(hdr + 8, &req_id, 4); + memcpy(hdr + 12, &len, 4); + if (!write_all(fd, hdr, sizeof hdr)) return -1; + if (len && payload && !write_all(fd, payload, len)) return -1; + return 0; +} + +/* 读一帧头+负载;返回负载长(≥0),-1=断/坏帧。 */ +static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (!read_all(fd, hdr, sizeof hdr)) return -1; + uint32_t magic, len; uint16_t ver; + memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(type, hdr + 6, 2); + memcpy(&len, hdr + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1; + if (len > rcap) len = rcap; + if (len > 0 && !read_all(fd, rbuf, len)) return -1; + return (int)len; +} + +static void media_app_id(void) +{ + if (g_app_set) return; + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app"); + g_app_set = 1; +} + +/* (调用方已持 mu)连接 + HELLO(MF_PLAYBACK) + 读 ACK。成功存 g_fd 返回 fd;失败 -1。 */ +static int conn_locked(void) +{ + if (g_fd >= 0) return g_fd; + int fd = connect_path(sock_path()); + if (fd < 0) return -1; + media_app_id(); + m_hello_t h; + memset(&h, 0, sizeof h); + snprintf(h.app_id, sizeof h.app_id, "%s", g_app); + h.version = 1; + h.flags = MF_PLAYBACK; + if (send_frame(fd, MSG_HELLO, 1, &h, sizeof h) != 0) { close(fd); return -1; } + uint8_t rbuf[256]; + uint16_t rt; + if (read_frame(fd, &rt, rbuf, sizeof rbuf) < 0 || + (rt != MSG_ACK && rt != MSG_ERR)) { close(fd); return -1; } + g_fd = fd; + return fd; +} + +/* (调用方已持 mu)标记断线并关闭。 */ +static void mark_dead_locked(void) +{ + if (g_fd >= 0) { close(g_fd); g_fd = -1; } +} + +void meow_ui_media_init(const char *app_id) +{ + pthread_mutex_lock(&g_mu); + if (app_id && app_id[0]) { + snprintf(g_app, sizeof g_app, "%s", app_id); + g_app_set = 1; + } else if (!g_app_set) { + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app, sizeof g_app, "%s", (s && s[0]) ? s : "app"); + g_app_set = 1; + } + pthread_mutex_unlock(&g_mu); +} + +void meow_ui_media_on_playctl(meow_media_playctl_fn cb) +{ + pthread_mutex_lock(&g_mu); + g_cb = cb; + int need_start = !g_thr_on; + if (need_start) { g_quit = 0; g_thr_on = 1; } + pthread_mutex_unlock(&g_mu); + if (need_start) pthread_create(&g_thr, NULL, media_thread_main, NULL); + /* detach:App 退出经 meow_ui_media_stop 设 g_quit 后线程自身退出,无需 join */ + if (need_start) pthread_detach(g_thr); +} + +void meow_ui_media_meta(uint32_t state, uint32_t pos_ms, uint32_t dur_ms, + const char *title, const char *artist, const char *album, + const char *artwork, const char *lyric) +{ + m_meta_t mm; + memset(&mm, 0, sizeof mm); + pthread_mutex_lock(&g_mu); + media_app_id(); + snprintf(mm.app_id, sizeof mm.app_id, "%s", g_app); + mm.state = state; + mm.pos_ms = pos_ms; + mm.dur_ms = dur_ms; + if (title) snprintf(mm.title, sizeof mm.title, "%s", title); + if (artist) snprintf(mm.artist, sizeof mm.artist, "%s", artist); + if (album) snprintf(mm.album, sizeof mm.album, "%s", album); + if (artwork && artwork[0]) { snprintf(mm.artwork, sizeof mm.artwork, "%s", artwork); mm.flags |= MF_ART | MF_COVER; } + if (lyric && lyric[0]) { snprintf(mm.lyric, sizeof mm.lyric, "%s", lyric); mm.flags |= MF_LYRIC; } + + int fd = conn_locked(); + if (fd >= 0) { + if (send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm) != 0) { + mark_dead_locked(); /* 断线:下次 meta 重连 */ + fd = conn_locked(); + if (fd >= 0) (void)send_frame(fd, MSG_META, (uint32_t)((uint64_t)pos_ms & 0x7FFFFFFF), &mm, sizeof mm); + } + } + pthread_mutex_unlock(&g_mu); +} + +void meow_ui_media_stop(void) +{ + pthread_mutex_lock(&g_mu); + g_quit = 1; + mark_dead_locked(); + g_thr_on = 0; + pthread_mutex_unlock(&g_mu); +} + +/* 后台线程主体:懒连 + poll 读 launcher 下发 MSG_PLAYCTL → 调 cb。 */ +static void *media_thread_main(void *arg) +{ + (void)arg; + while (!g_quit) { + pthread_mutex_lock(&g_mu); + int fd = g_fd; + if (fd < 0) fd = conn_locked(); /* 懒连/重连(best-effort;失败则回到循环再试) */ + pthread_mutex_unlock(&g_mu); + if (fd < 0) { usleep(500000); continue; } + + struct pollfd p; + p.fd = fd; p.events = POLLIN; p.revents = 0; + int pr = poll(&p, 1, 200); + if (pr < 0 && errno != EINTR) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & (POLLHUP | POLLERR | POLLNVAL))) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & POLLIN)) { + uint8_t rbuf[MEOW_IPC_MAX]; + uint16_t type; + int n = read_frame(fd, &type, rbuf, sizeof rbuf); + if (n < 0) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (type == MSG_PLAYCTL && n >= (int)sizeof(m_playctl_t)) { + m_playctl_t c; + memcpy(&c, rbuf, sizeof c); + /* g_cb 经互斥读快照后调用(App 回调可能在 UI 线程,简化为直接调) */ + pthread_mutex_lock(&g_mu); + meow_media_playctl_fn cb = g_cb; + pthread_mutex_unlock(&g_mu); + if (cb) cb((int)c.cmd, (int64_t)c.seek_ms); + } + } + } + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + return NULL; +} diff --git a/meow_ui_media.h b/meow_ui_media.h new file mode 100644 index 0000000..8bdcfae --- /dev/null +++ b/meow_ui_media.h @@ -0,0 +1,45 @@ +/* meow_ui_media.h — App 侧媒体会话封装(SDK 公共件;FRAMEWORK_DESIGN 附录 B / D1)。 + * + * 让框架/第三方播放类 App 以 SDK 标准上报实时元数据(MSG_META)并接收控制(MSG_PLAYCTL)。 + * 声明 MF_PLAYBACK 的 App 首次上报 → launcher 注册为媒体源(最近活跃=当前源);通知中心 + * 媒体卡片 / 主界面 Now Playing 页据此显示与控制。 + * + * 连接管理(best-effort):懒连(首次 meta/注册回调时 connect)+ 断线重连(下次上报时重试); + * launcher 不在 → 优雅静默降级,绝不崩。帧格式/负载复用 launcher 共享 meow_ipc.h(同一份)。 + * + * 命名:launcher 侧已占用 meow_media_*(meow_media.c),SDK 侧统一 meow_ui_media_* 前缀, + * 避免两态同符号在 launcher(链 meow_ui.a + meow_media.c)出现重复定义。 + */ +#ifndef MEOW_UI_MEDIA_H +#define MEOW_UI_MEDIA_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 播放控制回调:cmd 1=播放 2=暂停 3=上一首 4=下一首 5=seek(seek_ms 为毫秒)。 */ +typedef void (*meow_media_playctl_fn)(int cmd, int64_t seek_ms); + +/* 指定 app_id(NULL=env MEOW_LOG_APP,缺省 "app")。幂等,首次调用生效。 */ +void meow_ui_media_init(const char *app_id); + +/* 注册播放控制回调并启动后台接收线程(懒起;重复调用幂等)。 + * 线程:连接(HELLO+MF_PLAYBACK 注册为媒体源)→ poll 读 launcher 下发 MSG_PLAYCTL → 调 cb。 */ +void meow_ui_media_on_playctl(meow_media_playctl_fn cb); + +/* 上报实时元数据/进度/状态(state:0=停止 1=播放 2=暂停 3=缓冲)。 + * artwork/lyric 非空 → 自动置 MF_ART/MF_COVER/MF_LYRIC 标志;封面/歌词路径或 MeowOS 槽位。 + * best-effort:无 socket/launcher 不在 → 静默降级(打日志不崩)。 */ +void meow_ui_media_meta(uint32_t state, uint32_t pos_ms, uint32_t dur_ms, + const char *title, const char *artist, const char *album, + const char *artwork, const char *lyric); + +/* 停止后台接收线程并断开连接(App 退出时调;幂等)。 */ +void meow_ui_media_stop(void); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_MEDIA_H */ diff --git a/meow_ui_nav.c b/meow_ui_nav.c new file mode 100644 index 0000000..69897b4 --- /dev/null +++ b/meow_ui_nav.c @@ -0,0 +1,74 @@ +/* meow_ui_nav.c — App 侧 M3 导航上报封装(SDK 公共件;见 meow_ui_nav.h)。 + * + * 只做「组装 m_nav_t + 经 meow_notify_ipc 一次性发送 MSG_NAV」,不维护连接/订阅循环; + * launcher 主循环单写者消费(回复 ACK)。全部失败路径优雅降级(无 socket/写失败/超时 + * → 静默返回,不崩)。状态(栈深/顶层/app_id)仅进程内缓存,无并发写(App 页面线程调用)。 + */ +#include "meow_ui_nav.h" +#include "meow_ipc.h" +#include "meow_notify_ipc.h" /* meow_notify_ipc_send_raw(复用其一次性帧传输层) */ + +#include +#include +#include + +static char g_app_id[MEOW_APP_ID_MAX] = ""; +static int g_depth = 0; +static char g_top[MEOW_ACT_MAX] = ""; +static int g_inited = 0; + +/* app_id:env MEOW_LOG_APP(与 meow_ui_log/meow_notify_ipc 取值口径一致),缺省 "app"。 */ +static const char *nav_app_id(void) +{ + if (!g_app_id[0]) { + const char *s = getenv("MEOW_LOG_APP"); + snprintf(g_app_id, sizeof g_app_id, "%s", (s && s[0]) ? s : "app"); + } + return g_app_id; +} + +int meow_ui_nav_init(void) +{ + if (g_inited) return g_depth; + g_inited = 1; + nav_app_id(); + const char *chain = getenv("MEOW_CHAIN"); + if (chain && chain[0]) { + g_depth = 1; + for (const char *p = chain; *p; p++) if (*p == ';') g_depth++; + } + const char *act = getenv("MEOW_ACTIVITY"); + if (act && act[0]) snprintf(g_top, sizeof g_top, "%s", act); + return g_depth; +} + +int meow_ui_nav_depth(void) { meow_ui_nav_init(); return g_depth; } +const char *meow_ui_nav_top(void) { meow_ui_nav_init(); return g_top; } + +void meow_ui_nav_report(uint32_t depth, const char *top) +{ + meow_ui_nav_init(); + m_nav_t n; + memset(&n, 0, sizeof n); + snprintf(n.app_id, sizeof n.app_id, "%s", nav_app_id()); + n.nav_type = MEOW_NAV_REPORT; + n.depth = depth; + if (top) snprintf(n.current, sizeof n.current, "%s", top); + (void)meow_notify_ipc_send_raw(MSG_NAV, &n, (uint32_t)sizeof n, 1); + /* 进程内镜像:App 侧也维持当前栈深/顶层(跨 TU 只读用) */ + g_depth = (int)depth; + if (top) snprintf(g_top, sizeof g_top, "%s", top); +} + +void meow_ui_nav_request_exit(void) +{ + meow_ui_nav_init(); + m_nav_t n; + memset(&n, 0, sizeof n); + snprintf(n.app_id, sizeof n.app_id, "%s", nav_app_id()); + n.nav_type = MEOW_NAV_EXIT; + n.depth = 0; + (void)meow_notify_ipc_send_raw(MSG_NAV, &n, (uint32_t)sizeof n, 1); + g_depth = 0; + g_top[0] = 0; +} diff --git a/meow_ui_nav.h b/meow_ui_nav.h new file mode 100644 index 0000000..d786588 --- /dev/null +++ b/meow_ui_nav.h @@ -0,0 +1,42 @@ +/* meow_ui_nav.h — App 侧 M3 导航上报封装(SDK 公共件)。 + * + * App 用它把「应用内页面栈深/顶层 activity」经 IPC 报给 launcher(MSG_NAV REPORT), + * 以及在根屏按返回时请求退出(MSG_NAV EXIT);launcher 据此维护每 app 导航栈(纯上报, + * 回退边界由 launcher 统一决定)。App 不渲染系统 UI(FRAMEWORK_DESIGN §3 / MEOW_APP_SDK §6.1)。 + * + * 命名:launcher 侧已占用 meow_nav_*(app_launcher.c),SDK 侧统一 meow_ui_nav_* 前缀, + * 避免两态同符号在 launcher(链 meow_ui.a + app_launcher.c)出现重复定义。 + * + * 冷启动链路:launcher 经 MEOW_ACTIVITY= / MEOW_CHAIN=;;...; 注入; + * meow_ui_nav_init() 读之初始化栈深/顶层。App 自行 push/pop 页面后经 meow_ui_nav_report() + * 上报新深度/顶层。 + */ +#ifndef MEOW_UI_NAV_H +#define MEOW_UI_NAV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 从 env 初始化(幂等):MEOW_CHAIN 段数→栈深;MEOW_ACTIVITY→顶层 activity_id。 + * 未注入(裸启动)→ 深度 0、顶层空串。app_id 取 env MEOW_LOG_APP(缺省 "app")。 + * 返回当前逻辑深度(0=未注入/裸启动)。 */ +int meow_ui_nav_init(void); + +/* 当前栈深 / 顶层 activity_id(meow_ui_nav_init 后为其初值;App 上报后更新)。 */ +int meow_ui_nav_depth(void); +const char *meow_ui_nav_top(void); + +/* 上报 MEOW_NAV_REPORT:App 弹/推自己的屏后调用(depth=当前逻辑深度 1..16,top=顶层)。 + * best-effort:launcher 不在/无 socket 静默降级,不崩。 */ +void meow_ui_nav_report(uint32_t depth, const char *top); + +/* 上报 MEOW_NAV_EXIT:App 在根屏按返回,请求 launcher 终止应用回主屏。best-effort。 */ +void meow_ui_nav_request_exit(void); + +#ifdef __cplusplus +} +#endif +#endif /* MEOW_UI_NAV_H */ diff --git a/meow_ui_sys.c b/meow_ui_sys.c index aea79f7..b5fade9 100644 --- a/meow_ui_sys.c +++ b/meow_ui_sys.c @@ -15,6 +15,7 @@ #include "meow_ui_sys.h" #include "meow_ui_internal.h" #include "meow_ui_vol.h" +#include "meow_notify_ipc.h" /* meow_notify_ipc_notif_open:App 前台 #10.5 上报 */ #include #include @@ -256,12 +257,18 @@ int meow_ui_sys_statusbar_height(void) return meow_ui_statusbar_collapsed() ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; } -void meow_ui_sys_notif_pull(void) +void meow_ui_sys_notif_pull(int origin_y) { - /* 系统级「下拉展开通知中心」手势接线(M2.5x):输入层识别出「起始于顶部热区的 - * SWIPE_DOWN」后路由到此。通知中心 UI 为后续里程碑——此处先落记录(避免哑无痕); - * 届时由对应代理在该入口挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ - meow_log(MEOW_LOG_WARN, "notif_pull: notification center TBD (wired)"); + /* 系统级「下拉展开通知中心」手势接线(FRAMEWORK_DESIGN §10.5):输入层识别出「起始于 + * 顶部热区的 SWIPE_DOWN」后路由到此。App 进程只做检测+上报(不发事件给 on_swipe), + * 通知中心仍由 launcher 渲染——此处经 IPC 发 MSG_NOTIF_OPEN 请求 launcher 展开叠加会话 + * (SIGSTOP App + 读回其帧 + 叠加渲染通知中心)。best-effort:无 socket/launcher 不在 + * → meow_notify_ipc_notif_open 静默降级。 */ + static uint32_t seq = 0; + if (origin_y < 0) origin_y = 0; + meow_notify_ipc_notif_open(NULL, ++seq, 0, (uint16_t)origin_y, + (uint16_t)meow_ui_statusbar_hotzone(), + (uint8_t)(g_fullscreen ? 1 : 0)); } int meow_ui_sys_statusbar_force(void) diff --git a/meow_ui_sys.h b/meow_ui_sys.h index 4acfaa3..2a67ef9 100644 --- a/meow_ui_sys.h +++ b/meow_ui_sys.h @@ -64,10 +64,11 @@ int meow_ui_sys_statusbar_active(void); * MUI_SBAR_COLLAPSE_H(非满 28,app 内容可上移);否则 0(供 meow_ui_statusbar_height 用)。 */ int meow_ui_sys_statusbar_height(void); -/* 系统级「下拉展开通知中心」手势挂钩(M2.5x 接线):输入层识别出「起始于顶部热区的 - * SWIPE_DOWN」后路由到这里。通知中心 UI 为后续里程碑,此入口作记录/占位;届时由对应 - * 代理在此挂真实展开逻辑(IPC SHOW_ACTIVITY / 覆盖层渲染)。 */ -void meow_ui_sys_notif_pull(void); +/* 系统级「下拉展开通知中心」手势挂钩(§10.5):输入层识别出「起始于顶部热区的 SWIPE_DOWN」 + * 后路由到这里(origin_y=手势按下原点 Y,供 launcher 复核是否在顶部热区)。App 进程只做 + * 检测+上报:经 IPC 发 MSG_NOTIF_OPEN 请求 launcher 展开通知中心叠加会话;**App 不渲染系统 UI**。 + * best-effort:无 socket/launcher 不在 → 静默降级。 */ +void meow_ui_sys_notif_pull(int origin_y); /* 自上次渲染后状态栏数据是否已变(1s 轻刷/显隐切换置位);读取即清除。 * 渲染后端 present() 用它决定是否重画顶部条(配合脏区/整帧判断)。 */ diff --git a/test_ime_fix b/test_ime_fix index 14f793e155d26ea9cf7ba23b5b6345595900471c..c4d3ce7d842c56ffdd0a3e5df60cf3d154d39924 100755 GIT binary patch delta 71625 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 Q_-2j5geZl*2i@5J2PI#xU;qFB literal 304600 zcmeFa4V;}-b??8=%!GtYkdQ=q2{308ATa?G-b^BdIg_A40tF`N)vC9dA%wR8d8LG# zC?^TEl!)diS7=cjpuIwrHnrBv*j}ALwHBm?XsvC%dd^IO6tpGST8O)IN`yT zkaDiz=DSw@+vZMnM*&qyA(YlXuW#}N@i{KkYv!qT-wfsT qad`1fO923e$d%S#$ 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�h%=P0aAKT20=n{N#?RC<=({}}3GD3e%S8p6N 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