Files
MeowOS-SDK/meow_ui_dialog.c
T
MeowOS 7f36b6f069 feat(sdk): 控件库第二批 Material 子集(agent-152)
- 新增控件: 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 一致; 遗留真机观感/性能
2026-08-30 22:33:51 +08:00

294 lines
12 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.
/*
* 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
/* ---- 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); }