sync(SDK): 增量同步主仓 M6.37–M6.39 至公开 SDK 仓

sdk/:
- 同步 meow_ui_widget/input/gfx/lazygrid/audio/media/img 等源码与头
  (SDK 默认列表/容器控件默认 MD3 — meow_list_set_container 可回退;
   全局 App 内底部上滑回桌面 — meow_ui_input_is_home_swipe 系统保留手势;
   LazyGrid/LazyColumn/LazyRow MD3 化;叠加让位自抑改 app_id 判定;
   DSD 音频格式等)
- 新增独立模块源码 meow_img_color / meow_lyric / meow_lyric_view 及对应宿主 test_*.c
- 保留公开仓自包含适配:vendored meow_font.c/meow_font.h/meow_notif.h 不动;
  build_sdk.sh 自包含版本不动;meow_ui_gfx.c 仅改 meow_font.h include 路径;
  meow_ui_internal.h 去除 FreeType 内部声明
- meow_ipc.h 同步到主仓 launcher 版本(含 AUDIO_FMT_DOP/媒体歌词/蓝牙输出协议)
- 不含 FreeType 矢量字 meow_ft_*(需 freetype vendor,公开仓以位图 meow_font
  自包含);不含 mipsel 二进制 / .o / .a / 固件 / 密钥 / devdocs

examples/hello/:
- 新增 ui/demo/sdk2/sdk2.c;build_hello.sh 纳入该文件(保持自包含)

docs / README / .gitignore:
- docs/MEOW_APP_SDK.md 补 §12.5(默认 MD3 容器 + 底部上滑回桌面 + 两个新 API)
- README 能力清单补「默认 MD3 容器」与「底部上滑回桌面」
- .gitignore 补新增宿主测试二进制

验证:sdk/meow_ui.a 与 examples/hello/hello 交叉编译均 0 warning;工作区无生成物。
This commit is contained in:
AskaEth committed 2026-09-04 18:05:36 +08:00
1 parent a1687486bc
commit c0396e40ce
45 files changed
+4665 -175

No files matched your search

+2 -2
View File
@@ -10,8 +10,8 @@ cd "$(dirname "$0")"
CC="${MEOW_CC:-$HOME/Project/R3ProII/.tools/zig-mipsel-cc}"
SYSROOT="${MEOW_SYSROOT:-$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf}"
"$CC" -Os -static -Wall -Wextra -I"$(pwd)/../../sdk" -I. \
app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c ui/common/chrome.c \
logic/notify.c logic/state.c \
app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c ui/demo/sdk2/sdk2.c \
ui/common/chrome.c logic/notify.c logic/state.c \
"$(pwd)/../../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o hello
ls -la hello
file hello
+15
View File
@@ -106,6 +106,21 @@ int list_view_down(int x, int y); /* touch_down 命中列表:置
int list_view_move(int y); /* touch_move 拖动列表:1=在列表拖动 */
int list_view_swipe(int del); /* swipe:列表聚焦时滚列表,1=已消费 */
/* ---- SDK 批次2 控件演示区 CONTAINER 子视图(ui/demo/sdk2/sdk2.c)----
* 复刻 list.c 模式:demo 页内容区底部追加三节——枚举选择器 meow_select / 2D 虚拟化宫格
* meow_lazygrid / 列表行左滑删除 meow_listview_set_swipe_delete。子视图自持三节控件与视图态
* (grid 非 meow_widget:on_draw 直绘 meow_lazygrid_draw;swipe-list 行左滑 3 相位手势),
* 对外暴露 sdk2_build_at(demo_build_at 调)与 hit/down/move/swipe/release(demo 分发改派)。 */
meow_view *sdk2_view_create(void); /* 建 CONTAINER 子视图(须即 meow_view_add_child) */
meow_view *sdk2_view_get(void); /* 返回子视图(宿主测嵌套生命周期断言) */
int sdk2_build_at(int yb_base, int ct, int scroll); /* 在 base yb 处建三节控件(owner=ACT_DEMO),返回下一 yb */
void sdk2_release(void); /* 内容释放后:释放 grid/摘控件引用/清几何 */
int sdk2_hit(int x, int y); /* tap 分发:select 按钮/宫格 tap/列表行 tap/行左滑松手 swipe_end */
int sdk2_down(int x, int y); /* touch_down:宫格拖拽或列表行左滑 begin;1=本区消费 */
int sdk2_move(int x, int y); /* touch_move:宫格滚动/列表左滑 move 或列表竖向滚动;1=消费 */
int sdk2_swipe(int del); /* swipe UP/DOWN:宫格或列表聚焦时滚自身;1=已消费 */
int sdk2_dragging(void); /* 1=本子视图拖拽在途(demo_swipe 早退防 on_swipe 二次滚动) */
/* ---- chrome(ui/common/chrome.c)---- */
extern meow_widget *g_tb; /* TopAppBar(chrome 固定) */
extern meow_widget *g_nav; /* NavigationBar(chrome 固定) */
+66 -11
View File
@@ -43,6 +43,7 @@ static int g_drag_slider = 0; /* 手指按在 slider */
int g_drag_page = 0; /* 手指按在页内容(非 slider/list):拖拽滚页面 */
static int g_drag_page_last_y = 0; /* 页面拖拽上一帧 y */
static int g_drag_page_delta = 0; /* 页面拖拽累计位移(节流重建,≥8px 才重建一次) */
static uint64_t g_drag_page_tlast = 0; /* 页面拖拽上次重建时刻(359:每 poll 合并一次 rebuild) */
/* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */
@@ -157,6 +158,7 @@ static void demo_free_content(void)
if (g_slider_cid > 0) { meow_draw_unregister(g_slider_cid); g_slider_cid = 0; }
g_dtitle_n = 0;
list_view_release(); /* 列表控件已在上方 g_dslot 释放;摘容器引用/几何 */
sdk2_release(); /* SDK 批次2 子视图:释放 grid/btn/list 控件引用 + 清几何 */
}
/* 按 scroll 重建全部内容控件(owner=ACT_DEMO:调用方须已 push ACT_DEMO)。
@@ -246,6 +248,15 @@ static void demo_build_at(int scroll)
d_title(DCL_X, SY(yb), "MEDIA SESSION (meta)"); yb += 26;
r_media = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_media = meow_btn_create_variant(r_media.x, r_media.y, r_media.w, r_media.h, "SEND META", MEOW_BTN_FILLED); SLOT(g_dbtn_media, r_media.x, r_media.y, r_media.w, r_media.h); yb += DCH;
/* ---- SDK 批次2 新控件(meow_select / meow_lazygrid / swipe-delete)----
* 三节由 sdk2 CONTAINER 子视图(ui/demo/sdk2/sdk2.c)承接:章节标题/宫格/select 值文本走
* 子视图 on_draw 直绘(同列表区标题);select 按钮 + swipe 列表 widget 由 sdk2 自持、此处创建。
* 控件创建后几何已内存;跨重建视图态(grid scroll/sel、swipe 行集/scroll、select 值)由
* sdk2_build_at 从子视图持有态恢复。
* 注:sdk2 置于 LazyColumn 之前(列表保持 demo 页内容**末尾**区块),确保「滚动到底列表上屏」
* 的既有宿主 P3 断言坐标假设仍然成立(sdk2 增高在列表上方,列表仍是内容底)。 */
yb = sdk2_build_at(yb, ct, scroll);
/* ---- LazyColumn(100 项列表滚动) ----
* 本区块标题改由列表区 CONTAINER 子视图(ui/demo/list/list.c)的 on_draw 绘制(注册表→
* invalidate 链路);列表滚动/选中由 list_view_bind 从子视图持有态恢复(不再经 g_ds)。 */
@@ -268,7 +279,21 @@ void demo_paint(void)
meow_ui_paint_full(); /* 整帧按 z 序重画 */
}
/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用 */
/* 内容视口坐标(每次 build 同步):屏 y ∈ [g_demo_ct, g_demo_cb),x 跨 0..width。 */
static void demo_repaint_viewport(void)
{
int h = g_demo_cb - g_demo_ct;
if (h <= 0) return;
/* 滚动重绘:只清底+按 z 重画内容视口矩形 [0, ct, w, h],不整帧。
* 359 教训(滚动只刷视口 + 局部脏区不整屏重绘铁律):meow_ui_invalidate 内部已把绘制 clip
* 收敛到矩形(meow_gfx_clip_push)+ meow_ui_flush_region 只上屏该区;chrome(TopAppBar/
* NavBar/FAB,z=999,位于视口外)不触及,上一帧像素保留,滚动不再重画 chrome 字形。 */
meow_ui_invalidate(0, g_demo_ct, meow_ui_width(), h);
}
/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用。
* 滚动路径用视口局部刷(demo_repaint_viewport)替代整帧 demo_paint——每刻度重建仍发生,
* 但不再整帧重绘(359 教训:SDK 控件内建局部刷,卡顿源于调用方整页重绘)。 */
void demo_rebuild_to(int scroll)
{
int vp_h = g_demo_cb - g_demo_ct;
@@ -279,7 +304,7 @@ void demo_rebuild_to(int scroll)
demo_free_content();
demo_build_at(scroll);
g_demo_scroll = scroll;
demo_paint();
demo_repaint_viewport();
}
/* ---- demo 交互 ---- */
@@ -299,6 +324,8 @@ static int demo_content_tap(int x, int y)
/* 嵌套命中(子>父,§A.7 设计):列表区 CONTAINER 子视图先发,命中即消费。 */
if (list_view_hit(x, y)) return 1;
/* SDK 批次2 子视图(select 按钮/宫格/列表行左滑松手):命中即消费。 */
if (sdk2_hit(x, y)) return 1;
for (int i = 0; i < 5; i++)
if (meow_widget_hit(g_dbtn_b[i], x, y)) { d_toggle_sel(g_dbtn_b[i], r_btn[i]); g_ds.btn[i] = meow_widget_selected(g_dbtn_b[i]); return 1; }
@@ -344,8 +371,18 @@ static void demo_media_send(void)
}
/* demo 页 on_tap 分发(chrome → 内容) */
/* 页面拖拽抬手把节流窗内遗留的 delta 一次性重建(视口局部刷)——slow drag 结束为 TAP 时也精确落点。 */
static void demo_flush_page_delta(void)
{
if (g_drag_page && (g_drag_page_delta >= 1 || g_drag_page_delta <= -1)) {
demo_rebuild_to(g_demo_scroll + g_drag_page_delta);
g_drag_page_delta = 0;
}
}
void demo_on_tap(int x, int y)
{
demo_flush_page_delta(); /* 手势结束:TAP 抬手边沿(<SWIPE_THRESH),把遗留页面 delta 落到位 */
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return; }
@@ -389,44 +426,59 @@ static void demo_slider_drag(int x)
meow_ui_invalidate(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h); /* 重画值文本 */
}
/* demo 页 on_touch_down:锁一种拖拽(slider > list > 页内容)。
* 列表区走 list_view_down(子>父:命中列表区由列表 CONTAINER 子视图收口)。 */
/* demo 页 on_touch_down:锁一种拖拽(slider > list > sdk2 > 页内容)。
* 列表区走 list_view_down(子>父:命中列表区由列表 CONTAINER 子视图收口);
* SDK 批次2 子视图走 sdk2_down(宫格拖拽/列表行左滑 begin,子>父收口)。 */
void demo_touch_down(int x, int y)
{
g_drag_slider = 0; g_drag_list = 0; g_drag_page = 0; g_drag_page_delta = 0;
if (!g_demo) return;
if (meow_widget_hit(g_dslider, x, y)) { g_drag_slider = 1; demo_slider_drag(x); return; }
if (list_view_down(x, y)) return; /* 列表区:置 focus+drag_list(list.c) */
if (sdk2_down(x, y)) return; /* SDK 批次2:宫格/列表行左滑 begin(sdk2.c) */
g_demo_focus = 0;
if (y >= g_demo_ct && y < g_demo_cb && x >= 0 && x < meow_ui_width()) {
g_drag_page = 1; g_drag_page_last_y = y;
}
}
/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / 页面滚动节流重建)。 */
/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / sdk2 滚动 / 页面滚动节流重建)。 */
void demo_touch_move(int x, int y)
{
if (!g_demo) return;
if (g_drag_slider) { demo_slider_drag(x); return; }
if (list_view_move(y)) return; /* 列表拖动(list.c) */
if (sdk2_move(x, y)) return; /* SDK 批次2:宫格滚动/列表左滑 move/列表竖向滚动(sdk2.c) */
if (g_drag_page) {
int dy = y - g_drag_page_last_y; g_drag_page_last_y = y;
g_drag_page_delta += -dy; /* 手指上移(y 减) → scroll 增(显示更低内容),与 on_swipe 一致 */
if (g_drag_page_delta >= 8 || g_drag_page_delta <= -8) {
/* 359 教训:滚动期间每个 poll 合并一次 rebuild(勿每触摸事件各自重建/上屏)。
* 累计位移超阈值(≥8) 且(距上次 ≥16ms 或 已是大步 ≥32)才重建一次;未到窗则继续
* 累积 delta(不丢),下个窗口一次性重建到精确位置。 */
uint64_t now = meow_ui_time_ms();
int big = (g_drag_page_delta >= 8 || g_drag_page_delta <= -8);
int merge = (now - g_drag_page_tlast >= 16) ||
(g_drag_page_delta >= 32) || (g_drag_page_delta <= -32);
if (big && merge) {
demo_rebuild_to(g_demo_scroll + g_drag_page_delta);
g_drag_page_tlast = now;
g_drag_page_delta = 0;
}
}
}
/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动。 */
/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表/宫格/sdk2 聚焦滚自身,否则页滚动。 */
void demo_swipe(int dir, int dist)
{
if (dir == MEOW_SWIPE_LEFT) { meow_view_page_pop(); return; }
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
if (g_drag_page || g_drag_list) return; /* 本次手势已实时跟手滚动过 */
if (g_drag_page || g_drag_list || sdk2_dragging()) { /* 本次手势已实时跟手滚动过 */
demo_flush_page_delta(); /* 抬手:flush 节流窗内遗留页面 delta(视口局部刷)精确落点 */
return;
}
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; /* 手指上移 → 偏移增大 */
if (list_view_swipe(del)) return; /* 列表聚焦:滚列表(list.c,子>父消费) */
if (sdk2_swipe(del)) return; /* SDK 批次2:宫格/列表聚焦滚自身(sdk2.c,子>父消费) */
demo_rebuild_to(g_demo_scroll + del);
}
@@ -441,7 +493,8 @@ static void demo_page_enter(meow_view *v, void *ctx)
hello_nav_report(); /* ① 上报 demo 栈深/顶层 */
/* 列表区 CONTAINER 子视图随本页进入由 SDK view_enter 递归驱动(其 on_draw 在 demo_rebuild_to
* →demo_paint 中经注册表绘制;meow_draw_view_entered 含祖先已进入判定,首批可见)。 */
demo_rebuild_to(0);
demo_rebuild_to(0); /* 内容建到 scroll=0(视口局部刷) */
demo_paint(); /* 进入页整帧首绘(含 chrome 置顶 z=999);仅进入一次,非滚动路径 */
meow_log(MEOW_LOG_INFO, "demo enter scroll=0");
}
@@ -475,14 +528,16 @@ int demo_is_active(void)
}
/* demo view:on_create 建 chrome+内容(一次,owner=ACT_DEMO);进入时 rebuild 到顶。
* 视图深度化:先挂列表区 CONTAINER 子视图到本 ACTIVITY view 的 children,再建内容。
* 列表子视图 id 用非页面值,不占 g_view_by_id(见 list.c)。 */
* 视图深度化:先挂列表区 + SDK 批次2 两个 CONTAINER 子视图到本 ACTIVITY view 的 children,再建内容。
* 子视图 id 用非页面值,不占 g_view_by_id(见 list.c/sdk2.c)。 */
static void demo_page_create(meow_view *v, void *ctx)
{
(void)v;
((demo_ctrl *)ctx)->view = v;
meow_view *lv = list_view_create();
if (lv) meow_view_add_child(v, lv);
meow_view *s2 = sdk2_view_create();
if (s2) meow_view_add_child(v, s2);
demo_init();
}
+455
View File
@@ -0,0 +1,455 @@
/* ui/demo/sdk2/sdk2.c — 控件库 Demo 页内「SDK 批次2 新控件」CONTAINER 子视图(agent-363)。
*
* 目标(SDK_VIEW_FRAMEWORK_DESIGN.md §B.4/§E③,同 list.c):把 demo 页底部的三个 SDK 批次2
* 新控件演示区块抽成一个 meow_view CONTAINER 子视图,挂到 demo ACTIVITY view 的 children:
* ① meow_select (§5.8.9 枚举/预设选择器:下拉模态 + 单选列表 + on_change 回调)
* ② meow_lazygrid (§12.4 2 维虚拟化宫格:非 meow_widget,on_draw 直绘 meow_lazygrid_draw)
* ③ meow_listview 左滑删除(§5.5 swipe-delete:swipe_begin/move/end + on_delete + 回弹/删除动画)
*
* 职责边界(诚实,与 list.c 同折中):
* - 本子视图承接三节**整体**:章节标题 + select 按钮 + 宫格 + swipe-列表 的创建/释放与视图态
* (grid 虚拟源/选中/滚动、swipe 行集/滚动、select 选中值)。控件**创建**在 demo_build_at 链路内
* (sdk2_build_at,owner=ACT_DEMO)、**释放**在 sdk2_release(demo_free_content 调)——与 list.c
* 相同折中:完整「控件归容器拥有」需 SDK 侧支持(本批冻结 patch_v2/sdk/)。
* - meow_lazygrid 是**非 meow_widget**:不注册 z 序/绘制注册表。本子视图 on_draw 直接调
* meow_lazygrid_draw(view → 注册表 → invalidate 链路,与 list 区块标题同法)。
* - 章节标题(三段)与 select 当前值文本也走 on_draw 直绘(列表区标题同法),不逐标题注册
* CUSTOM 绘制物;on_draw 是回调、view 不缓存像素(非保留,§5.8.7)。
* - 嵌套生命周期由 SDK view_enter/view_leave 递归驱动(随 demo 页进出,同 list.c);绘制经
* meow_draw_view_entered(含祖先已进入判定)在 meow_ui_paint_full/invalidate 触发。
*
* 事件接线(demo 页把 touch/tap/swipe 分发改派到本子视图,实现「子>父」命中):
* - tap → sdk2_hit (select 按钮 / 宫格 tap / 列表行 tap / 行左滑松手 swipe_end)
* - down → sdk2_down (宫格聚焦拖拽 / 列表行左滑 begin)
* - move → sdk2_move (宫格滚动 / 列表左滑 move 或列表竖向滚动)
* - swipe → sdk2_swipe (宫格/列表聚焦时滚自身;否则回退页面滚动)
*/
#include "hello_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
/* ---- 本子视图(CONTAINER,非页面:id 用越界值避免占用 g_view_by_id 顶层页槽位)---- */
static meow_view *g_sdk2_view = NULL;
/* ---- demo 内容布局常量(与 demo.c 一致):内容区 x16 宽 448 ---- */
#define S2_X 16
#define S2_W 448
#define S2_DCW 212
#define S2_DBH 44
#define S2_CH 52
/* 三节(章节标题几何在 sdk2_build_at 计算后存入,on_draw 直绘用;屏幕坐标)。 */
static int s2_t1_y, s2_t2_y, s2_t3_y; /* 三节标题 y(on_draw 直绘) */
/* ============================================================ */
/* ① meow_select — 枚举/预设选择器 */
/* ============================================================ */
static meow_widget *g_btn_sel = NULL; /* 「打开选择器」按钮(sdk2_release 释放) */
static meow_select *g_sel = NULL; /* 选择器实例(sdk2_view_create 建一次,destroy 释放) */
static rect_t r_selbtn = { 0, 0, 0, 0 };
static rect_t r_selval = { 0, 0, 0, 0 }; /* 当前选择值文本区(on_draw 直绘) */
static int g_sel_cur = 0; /* 当前选中项(跨重建保留;on_change 更新) */
static const char *g_sel_items[] = { /* 枚举演示:音频档位(对齐 M7.2 设置页采样率/码率) */
"Auto", "44.1kHz", "48kHz", "96kHz", "192kHz",
};
#define SEL_N ((int)(sizeof g_sel_items / sizeof g_sel_items[0]))
/* on_change 回调(meow_select_open 确认后触发:更新选中下标 + 重画值文本区)。 */
static void sdk2_sel_change(int idx, void *ctx)
{
(void)ctx;
if (idx >= 0 && idx < SEL_N) g_sel_cur = idx;
if (r_selval.w > 0) meow_ui_invalidate(r_selval.x, r_selval.y, r_selval.w, r_selval.h);
}
/* 打开选择器(阻塞到确认/取消;on_change 已更新选中值)。 */
static void sdk2_select_open(void)
{
if (!g_sel) return;
int r = meow_select_open(g_sel);
meow_log(MEOW_LOG_INFO, "sdk2 select return=%d (%s)",
r, (r >= 0 && r < SEL_N) ? g_sel_items[r] : "cancel");
}
/* ============================================================ */
/* ② meow_lazygrid — 2 维虚拟化宫格(非 widget) */
/* ============================================================ */
static meow_lazygrid *g_grid = NULL;
static rect_t r_grid = { 0, 0, 0, 0 };
static int g_grid_scroll = 0; /* 跨重建保留:宫格滚动偏移 */
static int g_grid_sel = -1; /* 跨重建保留:选中项 */
static int g_drag_grid = 0; /* 触摸拖宫格(down 置位;move 滚动) */
static int g_grid_focus = 0; /* 1=宫格聚焦(swipe 滚宫格而非页面) */
static int g_grid_last_y = 0;
#define GRID_N 36 /* 总项数(count > 视口行数 → 可滚动) */
#define GRID_COLS 3
#define GRID_CW 138
#define GRID_CH 86
#define GRID_GAP 10
#define GRID_H 190
/* 虚拟源:count 总项数(实时查);bind 项进入视口时调一次(同 listview 池语义)。 */
static int grid_count(void *ctx) { (void)ctx; return GRID_N; }
static void grid_bind(int idx, meow_lazygrid_item *item, void *ctx)
{
(void)ctx;
static char t[24], s[24];
snprintf(t, sizeof t, "App %d", idx);
snprintf(s, sizeof s, "v1.%d", idx);
memset(item, 0, sizeof *item);
item->title = t; item->subtitle = s;
item->enabled = (idx % 9 != 8); /* 演示禁用变淡(每 9 个禁用 1 个) */
}
/* 完全自绘 cell(set_item_draw):icon+label 风格,对齐主界面用途。 */
static void grid_cell(int idx, int x, int y, int w, int h, int st, void *ctx)
{
(void)ctx;
const meow_theme *th = meow_theme_get();
int sel = st & MEOW_LAZYGRID_ST_SELECTED;
int dis = st & MEOW_LAZYGRID_ST_DISABLED;
uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow;
uint32_t icon = dis ? th->onSurfaceVariant : th->primary;
uint32_t txt = sel ? th->onPrimaryContainer
: (dis ? th->onSurfaceVariant : th->onSurface);
meow_ui_fill_rect(x, y, w, h, bg); /* 格底卡片 */
/* icon 块(无图资源:主题 primary 方块代替主界面图标位) */
int isz = w / 3; if (isz > h / 2) isz = h / 2;
meow_ui_fill_rect(x + (w - isz) / 2, y + 10, isz, isz, icon);
/* 名 + 状态(底部两行,中心对齐) */
static char t[24], s[24];
snprintf(t, sizeof t, "App %d", idx);
snprintf(s, sizeof s, "v1.%d", idx);
int thh = meow_ui_text_height(th->font_body);
meow_ui_text_center(x + w / 2, y + h - thh - 22, t, th->font_body, txt);
meow_ui_text_center(x + w / 2, y + h - thh - 3, s, th->font_body, txt);
if (sel) meow_ui_draw_rect_round(x, y, w, h, th->radius_card, th->primary);
if (dis) meow_ui_fill_rect_alpha(x, y, w, h, th->onSurface, 90);
}
/* 宫格 tap:置选中 + 局部刷 + 日志。 */
static void grid_tap(int idx, void *ctx)
{
(void)ctx;
g_grid_sel = idx;
meow_log(MEOW_LOG_INFO, "sdk2 grid tap idx=%d", idx);
if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h);
}
/* ============================================================ */
/* ③ meow_listview 行左滑删除(swipe-delete) */
/* ============================================================ */
#define LV_MAX 9
static meow_widget *g_lv = NULL; /* W_LISTVIEW(注册表自动画:行 + 右露删除区) */
static rect_t r_lv = { 0, 0, 0, 0 };
static int g_lv_deleted[LV_MAX]; /* 1=已删除(行移除;count 实时收缩) */
static char g_lv_label[LV_MAX][24]; /* 行标题(bind 填充) */
static int g_lv_scroll = 0; /* 跨重建保留:列表滚动 */
static int g_lv_focus = 0; /* 1=swipe-list 聚焦(swipe 滚列表而非页面) */
static int g_lv_swipe = 0; /* 行左滑在途(swipe_begin 命中;hit 里 swipe_end) */
static int g_drag_lv = 0; /* 触摸拖列表(竖向滚动);d. 左滑时同置位 */
static int g_lv_last_y = 0;
#define LV_H 190
static int lv_count_remaining(void)
{
int n = 0;
for (int i = 0; i < LV_MAX; i++) if (!g_lv_deleted[i]) n++;
return n;
}
static int lv_count(void *ctx) { (void)ctx; return lv_count_remaining(); }
/* 显示下标(0..count-1)→ 源行 id(跳过已删除行)。删除后行前移,身份按源行稳定。 */
static int lv_real_index(int display)
{
if (display < 0) return -1;
int c = 0;
for (int i = 0; i < LV_MAX; i++) {
if (g_lv_deleted[i]) continue;
if (c == display) return i;
c++;
}
return -1;
}
static void lv_bind(int idx, meow_lv_item *item, void *ctx)
{
(void)ctx;
int real = lv_real_index(idx);
static char s[24];
memset(item, 0, sizeof *item);
snprintf(s, sizeof s, "swipe left");
item->title = (real >= 0) ? g_lv_label[real] : "";
item->subtitle = s;
item->icon_kind = (real >= 0 && real % 3 == 0) ? MEOW_LIST_ICON_FOLDER : MEOW_LIST_ICON_NONE;
item->right = MEOW_LIST_RIGHT_NONE;
item->enabled = 1;
}
/* on_delete(swipe 删除动画滑出完成回调):删除该显示下标对应的源行 + 局部刷。
* 注:swipe_end 传的是显示下标(含删除前定位的 swipe_idx);映射回源行 id 标记。 */
static void lv_delete(int idx, void *ctx)
{
(void)ctx;
int real = lv_real_index(idx);
if (real >= 0) g_lv_deleted[real] = 1;
meow_log(MEOW_LOG_INFO, "sdk2 swipe delete display=%d real=%d", idx, real);
if (g_lv && r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h);
}
/* 建三节时填充行标题(demo_init 一次性;跨重建保留)。 */
static void sdk2_init_rows(void)
{
for (int i = 0; i < LV_MAX; i++) {
snprintf(g_lv_label[i], sizeof g_lv_label[i], "notif %d", i);
g_lv_deleted[i] = 0;
}
}
static int s2_built = 0; /* 1=本子视图内容已建(on_draw / 事件分发判空) */
/* ============================================================ */
/* 释放 / 建三节 */
/* ============================================================ */
/* 内容释放(demo_free_content 调):清引用(本子视图建的控件在 sdk2_release 释放)。 */
void sdk2_release(void)
{
if (g_grid) { meow_lazygrid_free(g_grid); g_grid = NULL; }
if (g_btn_sel) { meow_widget_free(g_btn_sel); g_btn_sel = NULL; }
if (g_lv) { meow_widget_free(g_lv); g_lv = NULL; }
r_selbtn = r_selval = r_grid = r_lv = RX(0, 0, 0, 0);
g_drag_grid = 0; g_grid_focus = 0; g_grid_last_y = 0;
g_lv_focus = 0; g_lv_swipe = 0; g_drag_lv = 0; g_lv_last_y = 0;
s2_built = 0;
if (g_sdk2_view) g_sdk2_view->x = g_sdk2_view->y = g_sdk2_view->w = g_sdk2_view->h = 0;
}
/* 内容重建后建三节(demo_build_at 调)。入参:yb_base=base 空间 y;ct/scroll 同 demo.c
* (SY(base)=ct+base-scroll)。返回下一 base y。 */
int sdk2_build_at(int yb_base, int ct, int scroll)
{
#define SY2(b) (ct + (b) - scroll)
int yb = yb_base;
/* ---- ① SELECT ---- */
s2_t1_y = SY2(yb);
yb += 26;
r_selbtn = RX(S2_X, SY2(yb), S2_DCW, S2_DBH);
g_btn_sel = meow_btn_create_variant(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h,
"CHOOSE", MEOW_BTN_OUTLINED);
r_selval = RX(S2_X + S2_DCW + 14, SY2(yb) + 10, S2_W - S2_DCW - 14, 24);
yb += S2_CH;
/* ---- ② LAZYGRID ---- */
s2_t2_y = SY2(yb);
yb += 30;
r_grid = RX(S2_X, SY2(yb), S2_W, GRID_H);
g_grid = meow_lazygrid_create(r_grid.x, r_grid.y, r_grid.w, r_grid.h,
GRID_COLS, GRID_CW, GRID_CH, GRID_GAP);
if (g_grid) {
meow_lazygrid_set_vsource(g_grid, grid_count, grid_bind, NULL);
meow_lazygrid_set_item_draw(g_grid, grid_cell, NULL);
meow_lazygrid_set_empty_text(g_grid, "No apps");
meow_lazygrid_set_on_tap(g_grid, grid_tap, NULL);
meow_lazygrid_set_scroll(g_grid, g_grid_scroll);
meow_lazygrid_set_sel(g_grid, g_grid_sel);
}
yb += GRID_H + 30;
/* ---- ③ SWIPE-DELETE ---- */
s2_t3_y = SY2(yb);
yb += 26;
r_lv = RX(S2_X, SY2(yb), S2_W, LV_H);
g_lv = meow_listview_create(r_lv.x, r_lv.y, r_lv.w, r_lv.h, 0,
NULL, NULL, MEOW_LIST_SEL_NONE);
if (g_lv) {
meow_listview_set_item_h(g_lv, 48);
meow_listview_set_vsource(g_lv, lv_count, lv_bind, NULL);
meow_listview_set_item_renderer(g_lv, 2, 1, 0); /* 双行、左图标、无右尾件 */
meow_listview_set_empty_text(g_lv, "All rows deleted");
meow_listview_set_swipe_delete(g_lv, 1, lv_delete, NULL);
meow_listview_set_scroll(g_lv, g_lv_scroll);
}
yb += LV_H + 10;
if (g_sdk2_view) {
g_sdk2_view->x = S2_X; g_sdk2_view->y = s2_t1_y;
g_sdk2_view->w = S2_W; g_sdk2_view->h = (r_lv.y + r_lv.h) - s2_t1_y;
}
s2_built = 1;
return yb;
#undef SY2
}
/* ---- on_draw:三节标题 + select 当前值 + 宫格(非保留,invalidate 触发重画)---- */
static void sdk2_draw_on(meow_view *v, const meow_theme *th, void *ctx)
{
(void)v; (void)th; (void)ctx;
if (!s2_built) return;
const meow_theme *t = meow_theme_get();
meow_ui_text(S2_X, s2_t1_y, "SELECT (enum/preset)", 20, t->primary);
meow_ui_text(S2_X, s2_t2_y, "LAZYGRID (2D virtualized)", 20, t->primary);
meow_ui_text(S2_X, s2_t3_y, "LISTVIEW swipe-delete", 20, t->primary);
char buf[40];
snprintf(buf, sizeof buf, "sel = %s", g_sel_items[g_sel_cur]);
meow_ui_text(r_selval.x, r_selval.y, buf, 20, t->dim);
if (g_grid) meow_lazygrid_draw(g_grid, t);
}
/* ============================================================ */
/* 事件分发(demo 页转发) */
/* ============================================================ */
/* tap:select 按钮 → 打开模态;列表行左滑松手 → swipe_end;宫格 → tap_at;列表行 → tap。 */
int sdk2_hit(int x, int y)
{
if (!s2_built) return 0;
/* 行左滑【松手】最优先:滑动后手指可能已移出列表矩形(只判在途标志)。 */
if (g_lv && g_lv_swipe) {
meow_listview_set_scroll(g_lv, g_lv_scroll);
int c = meow_listview_swipe_end(g_lv, x, y); /* 判删/回弹;动画由 meow_anim_tick 推进 */
g_lv_swipe = 0;
if (c) return 1; /* 消费(删/回弹动画中);未滑动=0 落行选中 */
}
if (g_btn_sel && meow_widget_hit(g_btn_sel, x, y)) {
sdk2_select_open();
meow_ui_invalidate(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h);
return 1;
}
if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) {
meow_lazygrid_tap_at(g_grid, x, y); /* 置选中 + grid_tap 回调 + 局部刷 */
return 1;
}
if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) {
meow_listview_tap_at(g_lv, x, y); /* 行点击(演示选中)+ 局部刷 */
meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h);
return 1;
}
return 0;
}
/* touch_down:命中区置拖拽/聚焦。宫格>列表。返回 1=本区消费。 */
int sdk2_down(int x, int y)
{
if (!s2_built) return 0;
/* 新手势先复位本子视图拖拽态:demo_touch_down 只复位 list/slider/page 标志,不触及 sdk2
* 的 g_drag_grid/g_drag_lv/g_lv_swipe(sdk2.c 内部,demo 页经 extern 看不到);不复位则上一条
* sdk2 拖拽残留会在本次「落页内容区」新手势的 move 里继续劫持页面拖动(sdk2_move 守卫误判)。 */
g_drag_grid = 0; g_drag_lv = 0; g_lv_swipe = 0;
if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) {
g_drag_grid = 1; g_grid_focus = 1; g_grid_last_y = y;
return 1;
}
if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) {
meow_listview_set_scroll(g_lv, g_lv_scroll);
g_lv_swipe = meow_listview_swipe_begin(g_lv, x, y); /* 命中可滑行 → 1 */
g_lv_focus = 1; g_drag_lv = 1; g_lv_last_y = y;
return 1;
}
return 0;
}
/* touch_move:宫格滚动 / 列表左滑 move(消费→不滚页面)或列表竖向滚动。返回 1=消费。 */
int sdk2_move(int x, int y)
{
if (!s2_built) return 0;
if (g_drag_grid && g_grid) {
int dy = y - g_grid_last_y; g_grid_last_y = y;
meow_lazygrid_scroll_by(g_grid, -dy);
g_grid_scroll = meow_lazygrid_scroll(g_grid);
if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h);
return 1;
}
/* 列表滚动须在「本次手势命中列表」才消费(g_drag_lv,由 sdk2_down 命中列表置位)。
* 若仅因 g_lv 存在就无条件消费,会劫持 demo 页内容区拖拽(任何 move 都被这里吃掉,
* 页面永不滚动)——与 grid(g_drag_grid 守卫)同构;359 教训同源(SDK 控件局部刷对,
* 错在调用方控制流把页面滚动让给了控件)。 */
if (g_drag_lv && g_lv) {
if (g_lv_swipe) { /* 行左滑在途:喂 move;1=横向消费;0=纵向主导→取消本行 swipe */
meow_listview_set_scroll(g_lv, g_lv_scroll);
if (meow_listview_swipe_move(g_lv, x, y)) return 1;
g_lv_swipe = 0;
}
int dy = y - g_lv_last_y; g_lv_last_y = y; /* 竖向滚动(跟手) */
meow_listview_scroll_by(g_lv, -dy);
g_lv_scroll = meow_listview_scroll(g_lv);
if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h);
return 1;
}
return 0;
}
/* swipe UP/DOWN:宫格/列表聚焦时滚自身(del 已按方向加权),1=已消费。 */
int sdk2_swipe(int del)
{
if (!s2_built) return 0;
if (g_grid_focus && g_grid) {
meow_lazygrid_scroll_by(g_grid, del);
g_grid_scroll = meow_lazygrid_scroll(g_grid);
if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h);
return 1;
}
if (g_lv_focus && g_lv) {
meow_listview_scroll_by(g_lv, del);
g_lv_scroll = meow_listview_scroll(g_lv);
if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h);
return 1;
}
return 0;
}
/* 本次手势是否已被本子视图实时滚过(demo_swipe 早退用,防 on_swipe 二次滚动——同 list 的 g_drag_list)。 */
int sdk2_dragging(void)
{
return (g_drag_grid || g_drag_lv) ? 1 : 0;
}
/* ============================================================ */
/* 子视图生命周期(on_enter/on_leave 由 SDK 递归驱动;on_destroy 清引用) */
/* ============================================================ */
static void sdk2_page_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
meow_log(MEOW_LOG_INFO, "demo sdk2 view enter");
}
static void sdk2_page_leave(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
meow_log(MEOW_LOG_INFO, "demo sdk2 view leave");
}
static void sdk2_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_sel) { meow_select_free(g_sel); g_sel = NULL; }
s2_built = 0;
g_sdk2_view = NULL; /* meow_view_free 递归释放前清引用,防悬垂 */
}
meow_view *sdk2_view_create(void)
{
static const meow_view_hooks h = {
.on_enter = sdk2_page_enter,
.on_leave = sdk2_page_leave,
.on_destroy = sdk2_page_destroy,
.on_draw = sdk2_draw_on,
};
/* id 用非页面值:容器不占 g_view_by_id(那是 page_push/pop 顶层页查找表)。 */
g_sdk2_view = meow_view_create(0x2000, MEOW_VIEW_CONTAINER, &h, NULL);
/* meow_select 实例一次性创建(控制态随本子视图存活,不受内容重建影响);行标题一次性填充。 */
sdk2_init_rows();
if (g_sdk2_view && !g_sel) {
g_sel = meow_select_create("Sample rate", 0);
if (g_sel) {
for (int i = 0; i < SEL_N; i++) meow_select_add_item(g_sel, g_sel_items[i]);
meow_select_set_on_change(g_sel, sdk2_sel_change, NULL);
}
}
return g_sdk2_view;
}
meow_view *sdk2_view_get(void)
{
return g_sdk2_view;
}