Files
MeowOS-For-R3P2/patch_v2/hello_app/hello.c
T

720 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* hello.c — MeowOS SDK 版示例应用(链 meow_ui.a,B1)。
*
* 契约(MEOW_APP_SDK.md §1/§3/§6):应用只调 meow_ui API —— 屏幕(back_buf +
* flush 上屏)与输入(meow_ui_run 事件循环)由框架接管;不再 open/mmap/ioctl
* /dev/fb0、不再读 /dev/input/event*。
*
* 两大职责:
* A) 原有:验证「清屏 + 画文本 + 画按钮 + 响应触摸」最小闭环 + 通知触发按钮
* (meow_notify_send,§9.1)端到端测试通知链路;
* B) 新增:**控件库 Demo 演示页**(§5.8 第一/二批组件)——作为 SDK 控件库的
* 验证载体 + 活文档:用真实控件(meow_btn_* / meow_switch_* / meow_slider_* /
* meow_widget_custom_create / meow_*)搭「展示 + 可交互」界面,用户刷 hello
* 从 home 的「WIDGETS DEMO」按钮进入,逐项验证各组件外观 / 命中 / 状态。
*
* 交互:
* - home:点 TAP ME → 回圈计数;6 个通知触发行;CANCEL LAST;FULLSCREEN;
* 新增「WIDGETS DEMO」按钮 → 进入 demo 页;左缘左滑 → 退出。
* - demo 页:TopAppBar 返回箭头 / 左缘左滑 → 回 home;内容区展示并交互
* Button 五型(含禁用) / Switch / Slider / TextField(点开阻塞 IME) / Checkbox /
* Radio(单选组) / ProgressBar(线性+圆形带动画) / Card(嵌子项) / Divider /
* Badge / TabBar / NavigationBar(底部) / TopAppBar(顶部) / Dialog+BottomSheet /
* FAB / LazyColumn(100 项列表,swipe 滚动)。配色取 meow_theme 语义色板(随系统
* MD3/MIUIx + 明暗自动切,零硬编码品牌色)。交互局部刷新(meow_ui_flush_region,
* 不整屏、不冲状态栏)。
*
* 页面/Activity(§6.1 ①):home(根,ACT_HOME 0)、全屏(ACT_FULL 1)、demo(ACT_DEMO 2)
* 三态互斥;控件 owner=创建时 active Activity,命中只在 owner==active 生效(根治跨页
* 残留)。demo 控件在启动 push(ACT_DEMO) 下一次性创建(owner=ACT_DEMO),此后 home
* 态不命中不绘制、demo 态 home 控件不命中。
*
* 滚动实现(关键:SDK 控件不透明——结构体对外不可见,App 不能读/写控件坐标;也无
* 位置 setter):
* - 内容控件以**绝对坐标**创建(App 侧另存 rect_t 几何用于局部 flush/命中)。
* - 页滚动 = 按「base_y - scroll」**重建**内容控件(仍处于 ACT_DEMO,owner 不变);
* 各控件跨重建状态存于 g_ds(App 持有),重建后恢复。
* - LazyColumn 是第二滚动区:滚它时 meow_list_scroll_by + 列表区局部刷新,不重建。
* - 因内容仅略超一屏(max_scroll 小),一次滑动仅触发 1~2 次重建,代价可接受。
*
* 宿主/qemu 离屏:设 MEOW_UI_DUMP=<ppm 路径> 时,首帧 flush 即把 back_buf 写成
* PPM 截图供检查;设备上无副作用。
*
* Build(见 build_hello.sh):
* zig-mipsel-cc -Os -static hello.c meow_ui.a libz.a -lpthread -o hello
*/
#include "meow_ui.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
/* §6.1 ①:Activity 归属。home=根(0);全屏=独立 Activity(无控件);demo=独立 Activity。 */
#define ACT_HOME 0
#define ACT_FULL 1
#define ACT_DEMO 2
/* ---- home 既有控件 ---- */
static meow_widget *g_btn;
static meow_widget *g_nbtn[6]; /* 通知触发按钮行 */
static meow_widget *g_cancel; /* 取消最近一条通知 */
static meow_widget *g_fs_btn; /* FULLSCREEN 进入按钮(主界面) */
static meow_widget *g_demo_btn; /* 「控件库 Demo」入口按钮(主界面,新增) */
static int g_fullscreen = 0; /* 1=处于全屏视图(点任意处退出回主界面) */
static const char *g_notif_labels[6] = { "INFO", "MEDIA", "HIGH", "PROG", "ONGO", "TGT" };
static uint32_t g_last_notif = 0; /* 最近一次 send 返回的 id,供 cancel */
static int g_taps = 0;
static int g_secs = 0;
static char g_btn_label[32] = "TAP ME";
/* ---- demo 演示页状态 ---- */
static int g_demo = 0; /* 1=处于 demo 演示页 */
static int g_demo_scroll = 0; /* 内容区滚动偏移(base 空间 px) */
static int g_demo_focus = 0; /* 1=LazyColumn 聚焦(swipe 滚列表而非页面) */
static int g_demo_fab = 0; /* FAB 点击计数反馈 */
static int g_demo_prog = 0; /* ProgressBar 动画当前值 0..100 */
static uint64_t g_demo_prog_last = 0;
static int g_demo_ct = 0; /* 内容视口顶(屏幕 y,= statusbar+56+8) */
static int g_demo_cb = 0; /* 内容视口底(屏幕 y,=608,NavBar 上方留白) */
static int g_demo_content_h = 0; /* 内容总高(base 空间;决定 max_scroll) */
/* M2.71-bug2/6:拖动过程(跟手)状态。on_touch_down 锁一种拖拽,on_touch_move 期间实时更新。 */
static int g_drag_slider = 0; /* 手指按在 slider:拖拽改值(按即定位) */
static int g_drag_list = 0; /* 手指按在 LazyColumn:拖拽滚列表 */
static int g_drag_page = 0; /* 手指按在页内容(非 slider/list):拖拽滚页面 */
static int g_drag_list_last_y = 0; /* 列表拖拽上一帧 y(求增量) */
static int g_drag_page_last_y = 0; /* 页面拖拽上一帧 y */
static int g_drag_page_delta = 0; /* 页面拖拽累计位移(节流重建,≥8px 才重建一次) */
/* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */
typedef struct { int x, y, w, h; } rect_t;
#define RX(x,y,w,h) ((rect_t){ (x), (y), (w), (h) })
/* demo 各控件跨重建保留的状态(app 持有;重建后按此恢复) */
static struct {
int sw_on; int slider; char tf[32];
int ck; int r1, r2; int btn[5]; int card; int tab; int list; int list_sel; int prog;
} g_ds = { .sw_on = 1, .slider = 40, .r1 = 1, .tab = 1, .list = 0, .list_sel = 0, .prog = 0 };
/* demo 内容布局常量(base 空间) */
#define DCL_X 16
#define DCR_X 252
#define DCW 212
#define DFW 448
#define DBH 44
#define DCH 52 /* 一行两列按钮步进 */
/* demo 内容槽:控件 + 其当前屏幕几何(rebuild 时重算) */
#define D_SLOT_MAX 64
static struct { meow_widget *w; rect_t r; } g_dslot[D_SLOT_MAX];
static int g_dslot_n = 0;
/* demo 分区小标题(纯文本,非控件;屏幕坐标在 rebuild 时算好) */
#define D_TITLE_MAX 16
static struct { int x, y; const char *s; } g_dtitle[D_TITLE_MAX];
static int g_dtitle_n = 0;
/* demo 交互组件引用(rebuild 时 g_* 与 r_* 同步重指/recompute) */
static meow_widget *g_tb; static rect_t r_tb; /* TopAppBar(chrome 固定) */
static meow_widget *g_nav; static rect_t r_nav; /* NavigationBar(chrome 固定) */
static meow_widget *g_fab; static rect_t r_fab; /* FAB(chrome 固定,可重建换计数 label) */
static meow_widget *g_dbtn_b[5]; static rect_t r_btn[5]; /* Button 五型 */
static meow_widget *g_dbtn_d; /* DISABLED 按钮(禁用不命中,无需 rect) */
static meow_widget *g_dsw; static rect_t r_dsw; /* Switch */
static meow_widget *g_dslider; static rect_t r_slider, r_slider_txt; /* Slider + 值文本 */
static meow_widget *g_dtf; static rect_t r_dtf; /* TextField */
static meow_widget *g_dcb; static rect_t r_dcb; /* Checkbox */
static meow_widget *g_dr1, *g_dr2; static rect_t r_dr1, r_dr2; /* Radio 单选组 */
static meow_widget *g_dprl; static rect_t r_prl; /* ProgressBar 线性 */
static meow_widget *g_dprc; static rect_t r_prc; /* ProgressBar 圆形 */
static meow_widget *g_dcard; static rect_t r_card; /* Card */
static meow_widget *g_dcard_btn; static rect_t r_cardbtn; /* Card 内容子按钮 */
static meow_widget *g_dbadge; /* Badge */
static meow_widget *g_dtab; static rect_t r_tab; /* TabBar */
static meow_widget *g_dbtn_dlg; static rect_t r_dlg; /* 打开 Dialog 的按钮 */
static meow_widget *g_dbtn_sheet; static rect_t r_sheet;/* 打开 BottomSheet 的按钮 */
static meow_widget *g_dlc; static rect_t r_list; /* LazyColumn */
/* Dialog / BottomSheet(阻塞模态;非控件,main() 创建一次) */
static meow_dialog *g_dlg;
static meow_sheet *g_sheet;
/* ---- demo 分区标题/slider 值文本:注册为 CUSTOM 绘制物(§6.2 区域重画会重画它们)----
* 显式 z=D_TITLE_Z:> 一切自动 z(=127)、< chrome 置顶(999),故「内容→标题→slider值→chrome」。
* 非控件直绘必须注册,否则 meow_ui_invalidate 清底后会漏画(非保留模型固有代价)。 */
#define D_TITLE_Z 128
static int g_dtitle_cid[D_TITLE_MAX]; /* 分区标题绘制物 id(rebuild 时注销/重注册) */
static int g_slider_cid = 0; /* slider 值文本绘制物 id */
/* CUSTOM 绘制回调:分区标题(ctx=title 下标,指向 g_dtitle[i]) */
static void demo_draw_title(void *ctx)
{
int i = (int)(intptr_t)ctx;
if (i < 0 || i >= g_dtitle_n) return;
const meow_theme *th = meow_theme_get();
meow_ui_text(g_dtitle[i].x, g_dtitle[i].y, g_dtitle[i].s, 20, th->primary);
}
/* CUSTOM 绘制回调:slider 当前值文本(bbox=r_slider_txt) */
static void demo_draw_slider_val(void *ctx)
{
(void)ctx;
if (!g_dslider) return;
const meow_theme *th = meow_theme_get();
char buf[28]; snprintf(buf, sizeof buf, "slider = %d", meow_slider_get(g_dslider));
meow_ui_text(r_slider_txt.x, r_slider_txt.y, buf, 20, th->dim);
}
/* 前向声明 */
static void redraw(void);
static void demo_rebuild_to(int scroll);
static void demo_paint(void);
static void demo_slider_drag(int x);
/* M2.51:统一日志改用 SDK meow_log()。 */
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); }
/* ============================================================ */
/* 通知触发(home 既有) */
/* ============================================================ */
static void notif_send(int which)
{
meow_notify n;
memset(&n, 0, sizeof n);
n.progress = -1;
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER;
n.urgency = MEOW_NOTIFY_URG_NORMAL;
const char *label = "?";
switch (which) {
case 0: label = "INFO"; n.cat = MEOW_NOTIFY_CAT_GENERAL;
n.title = "测试-普通通知"; n.body = "普通 INFO 通知,timeout=5000ms"; n.timeout_ms = 5000; break;
case 1: label = "MEDIA"; n.cat = MEOW_NOTIFY_CAT_MEDIA; n.urgency = MEOW_NOTIFY_URG_LOW;
n.title = "测试-媒体通知"; n.body = "媒体单例,正在播放,timeout=-1 常驻";
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; break;
case 2: label = "HIGH"; n.cat = MEOW_NOTIFY_CAT_GENERAL; n.urgency = MEOW_NOTIFY_URG_HIGH;
n.title = "测试-紧急度HIGH"; n.body = "高紧急度:顶部横幅+提示音"; n.timeout_ms = 3000; break;
case 3: label = "PROG"; n.cat = MEOW_NOTIFY_CAT_PROGRESS;
n.title = "测试-进度通知"; n.body = "下载进度 40%"; n.timeout_ms = 8000; n.progress = 40; break;
case 4: label = "ONGO"; n.cat = MEOW_NOTIFY_CAT_GENERAL;
n.title = "测试-常驻通知"; n.body = "常驻不自动过期(timeout=-1),需手动取消";
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; break;
case 5: label = "TGT"; n.cat = MEOW_NOTIFY_CAT_MESSAGE;
n.title = "测试-跳转Activity"; n.body = "点击跳转 target_activity=main";
n.timeout_ms = 6000; n.target_activity = "main"; n.icon = NULL; break;
default: return;
}
uint32_t id = meow_notify_send(&n);
g_last_notif = id;
meow_log(MEOW_LOG_INFO,
"notify btn=%s id=%u cat=%s urg=%s tmo=%d prog=%d target=%s title=%s",
label, (unsigned)id, meow_notify_category_str(n.cat),
meow_notify_urgency_str(n.urgency), n.timeout_ms, n.progress,
n.target_activity ? n.target_activity : "-", n.title ? n.title : "-");
}
static void notif_cancel(void)
{
int r = meow_notify_cancel(g_last_notif);
meow_log(MEOW_LOG_INFO, "notify cancel id=%u result=%d", (unsigned)g_last_notif, r);
g_last_notif = 0;
}
static void set_fullscreen(int on)
{
g_fullscreen = on ? 1 : 0;
meow_ui_set_fullscreen(g_fullscreen);
meow_ui_statusbar_set_collapsed(g_fullscreen);
if (on) meow_ui_activity_push(ACT_FULL);
else meow_ui_activity_pop();
meow_log(MEOW_LOG_INFO, g_fullscreen ? "fullscreen ON (collapsed), activity->%d"
: "fullscreen OFF (restore), activity->%d",
meow_ui_activity_active());
}
/* ============================================================ */
/* demo 演示页:布局 / 绘制 / 交互 */
/* ============================================================ */
static void d_slot(meow_widget *w, rect_t r)
{
if (g_dslot_n < D_SLOT_MAX) { g_dslot[g_dslot_n].w = w; g_dslot[g_dslot_n].r = r; g_dslot_n++; }
}
static void d_title(int x, int y, const char *s)
{
if (g_dtitle_n < D_TITLE_MAX) {
int i = g_dtitle_n;
g_dtitle[i].x = x; g_dtitle[i].y = y; g_dtitle[i].s = s;
g_dtitle_n++;
/* 注册为 CUSTOM 绘制物(bbox=文本绘制范围;cb 读 g_dtitle[i]) */
int tw = meow_ui_text_width(s, 20);
int th = meow_ui_text_height(20);
g_dtitle_cid[i] = meow_draw_custom_register(D_TITLE_Z, x, y, tw, th,
demo_draw_title, (void *)(intptr_t)i);
}
}
/* LazyColumn 数据源(100 项虚拟化) */
static int demo_list_src(int idx, char *label, int len, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 100) return 0;
snprintf(label, (size_t)len, "list item %d", idx);
return 1;
}
/* 释放当前全部内容控件(含 Card 的独立子按钮)与 CUSTOM 绘制物;chrome 不在其列。 */
static void demo_free_content(void)
{
for (int i = 0; i < g_dslot_n; i++) meow_widget_free(g_dslot[i].w);
g_dslot_n = 0;
if (g_dcard_btn) { meow_widget_free(g_dcard_btn); g_dcard_btn = NULL; }
for (int i = 0; i < g_dtitle_n; i++)
if (g_dtitle_cid[i] > 0) { meow_draw_unregister(g_dtitle_cid[i]); g_dtitle_cid[i] = 0; }
if (g_slider_cid > 0) { meow_draw_unregister(g_slider_cid); g_slider_cid = 0; }
g_dtitle_n = 0;
}
/* 按 scroll 重建全部内容控件(owner=ACT_DEMO:调用方须已 push ACT_DEMO)。
* 控件以绝对坐标创建于 (x, base_y - scroll),几何同步写入 r_* / 槽;状态从 g_ds 恢复。 */
static void demo_build_at(int scroll)
{
int ct = meow_ui_statusbar_height() + 56 + 8;
g_demo_ct = ct; g_demo_cb = 608;
g_dslot_n = 0; g_dtitle_n = 0;
int yb = 0;
#define SY(base) (ct + (base) - scroll) /* base 空间 → 屏幕 y */
#define SLOT(w,x,y,ww,hh) do { rect_t _r=RX(x,y,ww,hh); d_slot((w),_r); } while(0)
/* ---- Buttons(五型 + 禁用) ---- */
d_title(DCL_X, SY(yb), "BUTTONS 5-variants"); yb += 26;
r_btn[0] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[0] = meow_btn_create_variant(r_btn[0].x, r_btn[0].y, DCW, DBH, "FILLED", MEOW_BTN_FILLED); SLOT(g_dbtn_b[0], r_btn[0].x, r_btn[0].y, r_btn[0].w, r_btn[0].h);
r_btn[1] = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_b[1] = meow_btn_create_variant(r_btn[1].x, r_btn[1].y, DCW, DBH, "TONAL", MEOW_BTN_TONAL); SLOT(g_dbtn_b[1], r_btn[1].x, r_btn[1].y, r_btn[1].w, r_btn[1].h);
yb += DCH;
r_btn[2] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[2] = meow_btn_create_variant(r_btn[2].x, r_btn[2].y, DCW, DBH, "OUTLINED", MEOW_BTN_OUTLINED); SLOT(g_dbtn_b[2], r_btn[2].x, r_btn[2].y, r_btn[2].w, r_btn[2].h);
r_btn[3] = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_b[3] = meow_btn_create_variant(r_btn[3].x, r_btn[3].y, DCW, DBH, "TEXT", MEOW_BTN_TEXT); SLOT(g_dbtn_b[3], r_btn[3].x, r_btn[3].y, r_btn[3].w, r_btn[3].h);
yb += DCH;
r_btn[4] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[4] = meow_btn_create_variant(r_btn[4].x, r_btn[4].y, DCW, DBH, "ELEVATED", MEOW_BTN_ELEVATED); SLOT(g_dbtn_b[4], r_btn[4].x, r_btn[4].y, r_btn[4].w, r_btn[4].h);
g_dbtn_d = meow_btn_create_variant(DCR_X, SY(yb), DCW, DBH, "DISABLED", MEOW_BTN_FILLED);
SLOT(g_dbtn_d, DCR_X, SY(yb), DCW, DBH);
meow_widget_set_enabled(g_dbtn_d, 0); /* 禁用态:不命中 + 变暗 */
yb += DCH;
for (int i = 0; i < 5; i++) meow_widget_set_selected(g_dbtn_b[i], g_ds.btn[i]);
SLOT(meow_divider_create(DCL_X, SY(yb), DFW, 0), DCL_X, SY(yb), DFW, 1); yb += 16;
/* ---- Input / State(Switch / Slider / TextField / Checkbox / Radio) ---- */
d_title(DCL_X, SY(yb), "INPUT & STATE"); yb += 26;
r_dsw = RX(DCL_X, SY(yb), 56, 32); g_dsw = meow_switch_create(r_dsw.x, r_dsw.y, g_ds.sw_on); SLOT(g_dsw, r_dsw.x, r_dsw.y, r_dsw.w, r_dsw.h); yb += 44;
r_slider_txt = RX(DCL_X, SY(yb), 160, 24); yb += 24; /* 加宽到覆盖值文本全宽(防局部刷漏尾) */
r_slider = RX(DCL_X, SY(yb), DFW, 40); g_dslider = meow_slider_create(r_slider.x, r_slider.y, r_slider.w, 0, 100, g_ds.slider); SLOT(g_dslider, r_slider.x, r_slider.y, r_slider.w, r_slider.h); yb += 48;
g_slider_cid = meow_draw_custom_register(D_TITLE_Z, r_slider_txt.x, r_slider_txt.y,
r_slider_txt.w, r_slider_txt.h,
demo_draw_slider_val, NULL);
r_dtf = RX(DCL_X, SY(yb), DFW, 44); g_dtf = meow_textfield_create(r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h, "Type here...", g_ds.tf, 32, MEOW_IME_ASCII); SLOT(g_dtf, r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h); yb += 54;
r_dcb = RX(DCL_X, SY(yb), 34, 34); g_dcb = meow_checkbox_create(r_dcb.x, r_dcb.y); SLOT(g_dcb, r_dcb.x, r_dcb.y, r_dcb.w, r_dcb.h);
r_dr1 = RX(DCR_X, SY(yb), 34, 34); g_dr1 = meow_radio_create(r_dr1.x, r_dr1.y); SLOT(g_dr1, r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h);
yb += 40;
r_dr2 = RX(DCR_X, SY(yb), 34, 34); g_dr2 = meow_radio_create(r_dr2.x, r_dr2.y); SLOT(g_dr2, r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h);
yb += 40;
meow_widget_set_selected(g_dcb, g_ds.ck);
meow_widget_set_selected(g_dr1, g_ds.r1);
meow_widget_set_selected(g_dr2, g_ds.r2);
/* ---- ProgressBar(线性 + 圆形,on_tick 动画) ---- */
d_title(DCL_X, SY(yb), "PROGRESS (animated)"); yb += 26;
r_prl = RX(DCL_X, SY(yb), DFW, 8); g_dprl = meow_progress_create(r_prl.x, r_prl.y, r_prl.w, r_prl.h, g_ds.prog); SLOT(g_dprl, r_prl.x, r_prl.y, r_prl.w, r_prl.h); yb += 16;
/* 圆形进度:r_prc = 整圆像素外接框(中心 ± (r+margin),r=17 → 40×40),使 d_redraw 的
* flush 区与控件 bbox(=真实绘制范围)一致,不漏刷外圈(bgg M2.71-bug1)。中心仍取原视觉点。 */
r_prc = RX(DCL_X + 400 + 17 - 20, SY(yb) + 17 - 20, 40, 40);
g_dprc = meow_progress_circular_create(r_prc.x + 20, r_prc.y + 20, 17, g_ds.prog);
SLOT(g_dprc, r_prc.x, r_prc.y, r_prc.w, r_prc.h); yb += 48;
/* ---- Container(Card / Badge / TabBar) ---- */
d_title(DCL_X, SY(yb), "CONTAINER"); yb += 26;
r_card = RX(DCL_X, SY(yb), DFW, 88); g_dcard = meow_card_create(r_card.x, r_card.y, r_card.w, r_card.h); SLOT(g_dcard, r_card.x, r_card.y, r_card.w, r_card.h);
r_cardbtn = RX(DCL_X + 16, SY(yb) + 20, DFW - 32, 40); g_dcard_btn = meow_btn_create_variant(r_cardbtn.x, r_cardbtn.y, r_cardbtn.w, r_cardbtn.h, "IN-CARD BUTTON", MEOW_BTN_TONAL);
meow_widget_set_selected(g_dcard_btn, g_ds.card);
meow_card_set_content(g_dcard, g_dcard_btn);
yb += 96;
g_dbadge = meow_badge_create(DCL_X + 60, SY(yb) + 10);
SLOT(g_dbadge, DCL_X + 52, SY(yb) + 2, 18, 18);
meow_badge_set(g_dbadge, 3);
yb += 30;
r_tab = RX(DCL_X, SY(yb), DFW, 36); g_dtab = meow_tabbar_create(r_tab.x, r_tab.y, r_tab.w, r_tab.h); SLOT(g_dtab, r_tab.x, r_tab.y, r_tab.w, r_tab.h);
meow_tabbar_add_tab(g_dtab, "Playlist");
meow_tabbar_add_tab(g_dtab, "Queue");
meow_tabbar_add_tab(g_dtab, "Now");
meow_tabbar_set_selected(g_dtab, g_ds.tab);
yb += 44;
/* ---- Modals(Dialog + BottomSheet) ---- */
d_title(DCL_X, SY(yb), "MODALS (blocking)"); yb += 26;
r_dlg = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_dlg = meow_btn_create_variant(r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h, "DIALOG", MEOW_BTN_OUTLINED); SLOT(g_dbtn_dlg, r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h);
r_sheet = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_sheet = meow_btn_create_variant(r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h, "SHEET", MEOW_BTN_OUTLINED); SLOT(g_dbtn_sheet, r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h);
yb += DCH;
/* ---- LazyColumn(100 项列表滚动) ---- */
d_title(DCL_X, SY(yb), "LIST (LazyColumn, tap+swipe)"); yb += 26;
r_list = RX(DCL_X, SY(yb), DFW, 150); g_dlc = meow_lazycolumn_create(r_list.x, r_list.y, r_list.w, r_list.h, 40, 100, demo_list_src, NULL);
SLOT(g_dlc, r_list.x, r_list.y, r_list.w, r_list.h);
meow_list_set_scroll(g_dlc, g_ds.list);
meow_list_set_selected(g_dlc, g_ds.list_sel); /* M2.71-bug8:跨重建保留选中行 */
yb += 158;
g_demo_content_h = yb;
#undef SY
#undef SLOT
}
/* demo 全页重绘:整帧按 z 序重画全部(set_page_bg 清底 + 注册表按 z 升序)。
* 此前手工遍历 g_dslot/g_dtitle/后画 chrome 已删;现在自动 z 排:内容(0..127)→标题/slider值(128)
* →chrome(999 置顶),与旧绘制序一致,但「清底+重画」由 SDK 统一按 z 保证(P1 修法下沉)。 */
static void demo_paint(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg); /* 页面底 = 主题 bg(区域/整帧重画清底用) */
meow_ui_paint_full(); /* 整帧按 z 序重画 */
}
/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用 */
static void demo_rebuild_to(int scroll)
{
int vp_h = g_demo_cb - g_demo_ct;
int maxs = g_demo_content_h - vp_h;
if (maxs < 0) maxs = 0;
if (scroll < 0) scroll = 0;
if (scroll > maxs) scroll = maxs;
demo_free_content();
demo_build_at(scroll);
g_demo_scroll = scroll;
demo_paint();
}
/* 局部刷新统一走 SDK meow_ui_invalidate(清底 + 按 z 重画相交绘制物 + 上屏);
* 旧 d_paint_region/d_redraw/d_rect_overlap(App 层 z 栈重画)已删除、下沉到 SDK(§6.2)。
* 局部刷新一控件:整区清底 + 重画重叠 z 栈由注册表完成。 */
static void d_toggle_sel(meow_widget *w, rect_t r)
{
if (!w) return;
meow_widget_set_selected(w, !meow_widget_selected(w));
meow_ui_invalidate(r.x, r.y, r.w, r.h);
}
/* Tap:落点在内容控件上,返回 1=已消费 */
static int demo_content_tap(int x, int y)
{
g_demo_focus = 0; /* 默认切回页滚动;仅命中列表时改置 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; }
if (meow_widget_hit(g_dr1, x, y)) { meow_widget_set_selected(g_dr1, 1); meow_widget_set_selected(g_dr2, 0); g_ds.r1 = 1; g_ds.r2 = 0; meow_ui_invalidate(r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h); meow_ui_invalidate(r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h); return 1; }
if (meow_widget_hit(g_dr2, x, y)) { meow_widget_set_selected(g_dr2, 1); meow_widget_set_selected(g_dr1, 0); g_ds.r2 = 1; g_ds.r1 = 0; meow_ui_invalidate(r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h); meow_ui_invalidate(r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h); return 1; }
if (meow_widget_hit(g_dcb, x, y)) { d_toggle_sel(g_dcb, r_dcb); g_ds.ck = meow_widget_selected(g_dcb); return 1; }
if (meow_widget_hit(g_dsw, x, y)) { g_ds.sw_on = !meow_switch_get(g_dsw); meow_switch_set(g_dsw, g_ds.sw_on); meow_ui_invalidate(r_dsw.x, r_dsw.y, r_dsw.w, r_dsw.h); return 1; }
if (meow_widget_hit(g_dslider, x, y)) { demo_slider_drag(x); return 1; }
if (meow_widget_hit(g_dtf, x, y)) {
meow_textfield_activate(g_dtf); /* 阻塞 IME,返回后刷新 + 状态回写 */
strncpy(g_ds.tf, meow_textfield_text(g_dtf), sizeof g_ds.tf - 1);
g_ds.tf[sizeof g_ds.tf - 1] = 0;
meow_ui_invalidate(r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h);
return 1;
}
if (meow_widget_hit(g_dcard_btn, x, y)) { d_toggle_sel(g_dcard_btn, r_cardbtn); g_ds.card = meow_widget_selected(g_dcard_btn); return 1; }
if (g_dtab) {
int t = meow_tabbar_hit(g_dtab, x, y);
if (t >= 0) { g_ds.tab = t; meow_tabbar_set_selected(g_dtab, t); meow_ui_invalidate(r_tab.x, r_tab.y, r_tab.w, r_tab.h); return 1; }
}
if (meow_widget_hit(g_dbtn_dlg, x, y)) { meow_dialog_show(g_dlg); meow_log(MEOW_LOG_INFO, "dialog result=%d", meow_dialog_result(g_dlg)); meow_ui_invalidate(r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h); return 1; }
if (meow_widget_hit(g_dbtn_sheet, x, y)) { int r = meow_sheet_open(g_sheet); meow_log(MEOW_LOG_INFO, "sheet result=%d", r); meow_ui_invalidate(r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h); return 1; }
if (meow_widget_hit(g_dlc, x, y)) {
g_demo_focus = 1;
int li = meow_list_tap_at(g_dlc, x, y); /* M2.71-bug8:命中行→置选中并高亮 */
if (li >= 0) { g_ds.list_sel = li; meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); }
return 1;
}
return 0;
}
/* demo 页 on_tap 分发(chrome → 内容) */
static void demo_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { g_demo = 0; meow_ui_activity_pop(); redraw(); return; }
}
if (g_fab && meow_widget_hit(g_fab, x, y)) {
g_demo_fab++;
char lb[16]; snprintf(lb, sizeof lb, "+%d", g_demo_fab);
meow_widget_free(g_fab);
g_fab = meow_fab_create(r_fab.x, r_fab.y, 56, lb);
meow_widget_set_z(g_fab, 999); /* 重建后重挂顶:新 entry 自动 z 低,须显式置顶 */
meow_ui_invalidate(r_fab.x, r_fab.y, r_fab.w, r_fab.h); /* 重画 FAB 及其下重叠 z 栈 */
return;
}
if (g_nav) {
int idx = meow_navigationbar_hit(g_nav, x, y);
if (idx >= 0) { meow_navigationbar_set_selected(g_nav, idx); meow_ui_invalidate(r_nav.x, r_nav.y, r_nav.w, r_nav.h); return; }
}
if (demo_content_tap(x, y)) return;
}
/* demo 页 on_tick:ProgressBar 动画(局部刷,~120ms 步进) */
static void demo_tick(uint64_t now)
{
if (g_demo_prog_last == 0) g_demo_prog_last = now;
if (now - g_demo_prog_last < 120) return;
g_demo_prog_last = now;
g_ds.prog = (g_ds.prog + 3) % 101;
g_demo_prog = g_ds.prog;
meow_progress_set(g_dprl, g_demo_prog); meow_ui_invalidate(r_prl.x, r_prl.y, r_prl.w, r_prl.h);
meow_progress_set(g_dprc, g_demo_prog); meow_ui_invalidate(r_prc.x, r_prc.y, r_prc.w, r_prc.h);
}
/* 按触碰 X 落 slider 值并局部刷(按下即定位 + 拖动实时,M2.71-bug2) */
static void demo_slider_drag(int x)
{
int frac = x - r_slider.x; if (frac < 0) frac = 0; if (frac > r_slider.w) frac = r_slider.w;
g_ds.slider = frac * 100 / r_slider.w;
meow_slider_set(g_dslider, g_ds.slider);
meow_ui_invalidate(r_slider.x, r_slider.y, r_slider.w, r_slider.h);
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 > 页内容)。非 demo 态跳过。
* P2:滚动焦点按「本次触摸起作用的控件」绑定、抬手/命中其它区域即释放——命中非 list
* 区域时不再沿用「已聚焦列表」的旧焦点去滚列表,而是切到页/上一层滚动(否则滑完列表再
* 滑页面,焦点仍在列表里继续滚列表,需额外点一下才出来)。 */
static 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 (g_dlc && meow_widget_hit(g_dlc, x, y)) {
g_demo_focus = 1; g_drag_list = 1; g_drag_list_last_y = y; return;
}
/* 未命中列表 → 释放列表焦点,本次手势滚页面 */
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 值 / 列表滚动 / 页面滚动节流重建)。 */
static void demo_touch_move(int x, int y)
{
if (!g_demo) return;
if (g_drag_slider) { demo_slider_drag(x); return; }
if (g_drag_list) {
int dy = y - g_drag_list_last_y; g_drag_list_last_y = y;
if (dy) { meow_list_scroll_by(g_dlc, -dy); g_ds.list = meow_list_scroll(g_dlc); meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); }
return;
}
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) {
demo_rebuild_to(g_demo_scroll + g_drag_page_delta);
g_drag_page_delta = 0;
}
}
}
/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动。
* M2.71-bug6:拖动过程已在 on_touch_move 实时跟手滚过,抬手不再重复滚(否则叠加一次全距跳变)。 */
static void demo_swipe(int dir, int dist)
{
if (dir == MEOW_SWIPE_LEFT) { g_demo = 0; meow_ui_activity_pop(); redraw(); return; }
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
if (g_drag_page || g_drag_list) return; /* 本次手势已实时跟手滚动过 */
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; /* 手指上移 → 偏移增大 */
if (g_demo_focus && g_dlc) {
meow_list_scroll_by(g_dlc, del);
g_ds.list = meow_list_scroll(g_dlc);
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); /* 列表区局部刷新 */
} else {
demo_rebuild_to(g_demo_scroll + del);
}
}
/* 进入 demo 演示页(重建到顶部) */
static void demo_enter(void)
{
g_demo = 1;
g_demo_focus = 0;
meow_ui_activity_push(ACT_DEMO);
demo_rebuild_to(0);
meow_log(MEOW_LOG_INFO, "demo enter scroll=0");
}
/* 启动时创建 chrome(固定)+ 内容(scroll=0);push(ACT_DEMO) 后创建使 owner=ACT_DEMO。 */
static void demo_init(void)
{
int sb = meow_ui_statusbar_height();
meow_ui_activity_push(ACT_DEMO);
r_tb = RX(0, sb, 480, 56); g_tb = meow_topappbar_create(0, sb, 480, 56, "Widgets Demo");
meow_topappbar_set_back(g_tb, 1);
r_nav = RX(0, 680, 480, 40); g_nav = meow_navigationbar_create(0, 680, 480, 40);
meow_navigationbar_add_item(g_nav, "Buttons");
meow_navigationbar_add_item(g_nav, "List");
meow_navigationbar_add_item(g_nav, "More");
meow_navigationbar_set_selected(g_nav, 0);
r_fab = RX(406, 616, 56, 56); g_fab = meow_fab_create(406, 616, 56, "+");
/* chrome 显式置顶(§6.2 置顶):> 一切自动 z(127),区域重画/整帧都最后画 chrome。 */
meow_widget_set_z(g_tb, 999);
meow_widget_set_z(g_nav, 999);
meow_widget_set_z(g_fab, 999);
demo_build_at(0);
meow_ui_activity_pop();
g_demo_scroll = 0;
}
/* ============================================================ */
/* 既有 home 绘制 / 事件 */
/* ============================================================ */
static void redraw(void)
{
const meow_theme *th = meow_theme_get();
int sb = meow_ui_statusbar_height();
if (g_fullscreen) {
meow_ui_clear(th->accent);
meow_ui_text_center(240, 330, "FULLSCREEN ACTIVE", 28, 0xFFFFFF);
meow_ui_text_center(240, 366, "tap anywhere = back to home", 20, 0xFFFFFF);
meow_ui_text_center(240, 394, "(left-edge swipe also returns)", 20, 0xFFFFFF);
meow_ui_flush();
return;
}
if (sb > 0) meow_ui_fill_rect(0, sb, 480, 720 - sb, th->bg);
else meow_ui_clear(th->bg);
meow_ui_text_center(240, sb + 8, "MEOWOS HELLO", 28, th->fg);
meow_ui_text_center(240, sb + 38, "SDK: meow_ui.a", 20, th->dim);
meow_widget_draw(g_btn);
meow_ui_text_center(240, 372, "tap = send notification (see log)", 20, th->dim);
for (int i = 0; i < 6; i++) meow_widget_draw(g_nbtn[i]);
meow_widget_draw(g_cancel);
meow_widget_draw(g_fs_btn);
meow_widget_draw(g_demo_btn);
char line[64];
snprintf(line, sizeof line, "taps=%d uptime=%ds", g_taps, g_secs);
meow_ui_text_center(240, 516, line, 20, th->dim);
meow_ui_text(20, 660, "left-swipe = exit", 20, th->dim);
meow_ui_flush();
}
static void on_tap(int x, int y)
{
if (g_demo) { demo_on_tap(x, y); return; }
if (g_fullscreen) { set_fullscreen(0); redraw(); return; }
if (meow_widget_hit(g_demo_btn, x, y)) { demo_enter(); return; }
for (int i = 0; i < 6; i++) {
if (meow_widget_hit(g_nbtn[i], x, y)) { notif_send(i); redraw(); return; }
}
if (meow_widget_hit(g_cancel, x, y)) { notif_cancel(); redraw(); return; }
if (meow_widget_hit(g_fs_btn, x, y)) { set_fullscreen(1); redraw(); return; }
if (meow_widget_hit(g_btn, x, y)) {
g_taps++;
snprintf(g_btn_label, sizeof g_btn_label, "TAPPED x%d", g_taps);
meow_widget_free(g_btn);
g_btn = meow_btn_create(120, 300, 240, 64, g_btn_label);
redraw();
}
}
static void on_key(int keycode, int is_down)
{
(void)keycode; (void)is_down;
meow_log(MEOW_LOG_DEBUG, "key");
}
static void on_swipe_progress(int x_start, int dx)
{
meow_ui_swipe_hint(x_start + dx);
}
/* M2.71-bug2/6:拖动过程回调(只对 demo 页有意义;home/全屏不拖拽)。 */
static void on_touch_down(int x, int y)
{
if (g_demo) demo_touch_down(x, y);
}
static void on_touch_move(int x, int y)
{
if (g_demo) demo_touch_move(x, y);
}
static void on_swipe(int dir, int dx, int dy)
{
(void)dy;
if (g_demo) { demo_swipe(dir, dx); return; }
if (dir == MEOW_SWIPE_LEFT) {
if (g_fullscreen) { set_fullscreen(0); redraw(); }
else { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); }
}
}
static void on_tick(void)
{
uint64_t now = meow_ui_time_ms();
if (g_demo) { demo_tick(now); return; }
static uint64_t last = 0;
if (now - last >= 1000) {
last = now;
g_secs++;
redraw();
}
}
int main(void)
{
meow_log(MEOW_LOG_INFO, "start");
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; }
meow_theme_add_listener(NULL);
signal(SIGTERM, on_sigterm);
g_btn = meow_btn_create(120, 300, 240, 64, g_btn_label);
for (int i = 0; i < 6; i++)
g_nbtn[i] = meow_btn_create(3 + i * 80, 400, 74, 56, g_notif_labels[i]);
g_cancel = meow_btn_create(36, 464, 200, 42, "CANCEL LAST");
g_fs_btn = meow_btn_create(244, 464, 200, 42, "FULLSCREEN");
g_demo_btn = meow_btn_create(36, 560, 408, 44, "WIDGETS DEMO");
/* Dialog / BottomSheet 实例(阻塞模态;打开前排空残留触摸由 SDK 处理) */
g_dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick a fruit");
const char *items[] = { "Apple", "Banana", "Cherry" };
meow_dialog_set_items(g_dlg, items, 3, 1);
g_sheet = meow_sheet_create("Options", 0);
meow_sheet_add_item(g_sheet, "Copy");
meow_sheet_add_item(g_sheet, "Delete");
meow_sheet_add_item(g_sheet, "Share");
demo_init();
redraw();
meow_ui_events ev = { on_key, on_tap, on_swipe, on_tick, on_swipe_progress,
on_touch_down, on_touch_move };
meow_ui_run(&ev);
meow_widget_free(g_demo_btn);
for (int i = 0; i < 6; i++) meow_widget_free(g_nbtn[i]);
meow_widget_free(g_cancel);
meow_widget_free(g_fs_btn);
meow_widget_free(g_btn);
/* demo 内容 + chrome */
demo_free_content();
meow_widget_free(g_tb);
meow_widget_free(g_nav);
meow_widget_free(g_fab);
meow_dialog_free(g_dlg);
meow_sheet_free(g_sheet);
meow_ui_shutdown();
meow_log(MEOW_LOG_INFO, "exit");
return 0;
}