Files
MeowOS-SDK/sdk/meow_ui_dialog.c
T
MeowOS f44e3bc6d0 Add 'sdk/' from commit '2e7561d74f75de05dd7fb362f203f600afb451d7'
git-subtree-dir: sdk
git-subtree-mainline: 17a972d46f
git-subtree-split: 2e7561d74f
2026-09-01 21:08:40 +08:00

389 lines
17 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
/* 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); }