Add 'sdk/' from commit '2e7561d74f75de05dd7fb362f203f600afb451d7'

git-subtree-dir: sdk
git-subtree-mainline: 17a972d46f
git-subtree-split: 2e7561d74f
This commit is contained in:
MeowOS committed 2026-09-01 21:08:40 +08:00
commit f44e3bc6d0
85 files changed
+23811

No files matched your search

+388
View File
@@ -0,0 +1,388 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MEOW_MODAL_MAX_ITEMS 32
#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;
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;
meow_ime_ctx *ime_orig; /* INPUT:App 传入的原始 ctx(回写最终文本,App 关闭后可读回) */
};
/* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */
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_card(meow_dialog *d, const meow_theme *th, const dlg_geom *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);
}
}
/* 整帧绘制:遮罩 + 卡片。 */
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);
}
/* 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);
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->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)
{
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;
d->ime_orig = NULL; /* 无 App 原始 ctx,关闭时不回写 */
dlg_prep_ime(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;
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) {
/* M2.71-bug3:点项 → 立即刷「选中」高亮(不下线、不叠加遮罩);
* 再点同一项 → 确认(返回该项下标)。点卡片外=取消。 */
int idx = (y - g.row_y) / g.rowh;
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;
}
/* 非选择:按钮命中 */
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); /* 阻塞编辑 */
/* 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;
}
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);
}
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
}
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;
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);
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)) {
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; } /* 点面板外=取消 */
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);
}
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
return s->result;
}
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }