/* * meow_select.c — §5.8.6 枚举/预设选择器(M7.2 设置页缺口 #4)。 * * 封装 = BottomSheet(标题 + 选项列表)+ meow_listview(SINGLE) 单选列表。选项用 meow_listview * 渲染(单选高亮 primaryContainer、可滚动 meow_scroll 惯性);外层模态复用 meow_ui_modal_* * + meow_ui_modal_capture/restore(与 meow_dialog 同源:scrim 只叠一层/关闭无残影)。 * 阻塞式(meow_input_pop 轮询);打开前排空残留触摸;点面板外/左缘返回 = 取消;点选项 = 选中 + * 关闭 + on_change 回调。选项数组定长(≤MEOW_SELECT_MAX_ITEMS)。 */ #include "meow_ui.h" #include "include/meow_select.h" #include "meow_ui_internal.h" /* meow_input_pop/drain + meow_ui_modal_capture/restore */ #include #include #include #define MEOW_SELECT_ROW 48 /* 选项行高(px) */ struct meow_select { char title[96]; char items[MEOW_SELECT_MAX_ITEMS][64]; int nitems; int cur; /* 初始/当前选中项 */ int result; /* 选中下标 / -1=取消 */ int done; int h; /* 面板高度(0=自动) */ meow_select_fn cb; void *ctx; }; /* meow_listview src2 数据源:填 title=options[idx](右尾件无;enabled=1)。 */ static int select_src2(int idx, meow_list_item *out, void *ctx) { meow_select *s = (meow_select *)ctx; if (idx < 0 || idx >= s->nitems) return 0; memset(out, 0, sizeof *out); out->title = s->items[idx]; out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 1; return 1; } meow_select *meow_select_create(const char *title, int cur) { meow_select *s = (meow_select *)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->cur = cur; s->result = -1; s->done = 0; return s; } int meow_select_add_item(meow_select *s, const char *label) { if (!s || s->nitems >= MEOW_SELECT_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; } void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx) { if (s) { s->cb = cb; s->ctx = ctx; } } int meow_select_open(meow_select *s) { if (!s || s->nitems <= 0) return -1; const meow_theme *th = meow_theme_get(); meow_input_start(); meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ s->done = 0; s->result = -1; int W = meow_ui_width(), H = meow_ui_height(); int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; int pad = 24; int title_h = meow_ui_text_height(th->font_title); int rowh = MEOW_SELECT_ROW; int avail = H - sb - 20; int min_h = pad + title_h + 12 + rowh + 20; int want = (s->h > 0) ? s->h : min_h; int est = pad + title_h + 12 + s->nitems * rowh + 20; if (est > want) want = est; if (want > avail) want = avail; if (want < min_h) want = (min_h < avail) ? min_h : avail; int ph = want, py = H - ph; /* 底部面板(顶部圆角) */ int list_y = py + pad + title_h + 12; int list_h = ph - (list_y - py) - pad; /* 底部余量 */ if (list_h < rowh) list_h = rowh; /* 选项列表(meow_listview[SINGLE]:单选高亮 + 可滚动)。open 时创建,close 释放(冷路径)。 */ meow_widget *lv = meow_listview_create(0, list_y, W, list_h, s->nitems, select_src2, s, MEOW_LIST_SEL_SINGLE); if (!lv) return -1; meow_listview_set_item_h(lv, rowh); /* 定位初始选中项在视口内。 */ int target = s->cur * rowh - (list_h - rowh) / 2; int maxs = meow_listview_max_scroll(lv); if (target < 0) target = 0; if (target > maxs) target = maxs; meow_listview_set_scroll(lv, target); meow_listview_set_sel(lv, s->cur); meow_scroll *sc = meow_scroll_wrap(lv, 1); /* 惯性滚动(fling/snap) */ /* §6.4 模态作用域 + 背景快照(关闭即还原,无残影/不叠黑)。 */ meow_ui_modal *m = meow_ui_modal_begin(); meow_ui_modal_capture(); /* 首次绘制:遮罩 + 底部面板(顶部圆角)+ 标题 + 选项列表。 */ 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); meow_widget_draw(lv); meow_ui_flush(); int dy_start = 0, down = 0; while (!s->done) { meow_input_ev e; while (meow_input_pop(&e)) { meow_ui_activity_ping(); /* 模态期间交互也重置息屏计时 */ if (e.type == MEOW_EV_TOUCH_DOWN) { down = 1; dy_start = e.b; } else if (e.type == MEOW_EV_TOUCH_MOVE) { if (down && sc) { meow_scroll_track(sc, e.b - dy_start, meow_ui_time_ms()); dy_start = e.b; } } else if (e.type == MEOW_EV_TAP) { int x = e.a, y = e.b; if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外 = 取消 */ int idx = meow_listview_tap_at(lv, x, y); /* 单选高亮 = 选中 */ if (idx >= 0) { s->result = idx; s->cur = idx; if (s->cb) s->cb(idx, s->ctx); /* 关闭即回调 */ s->done = 1; } /* 面板内但未命中项:保持打开(等下一次点击)。 */ } else if (e.type == MEOW_EV_SWIPE) { if (e.a == MEOW_SWIPE_LEFT) { s->done = 1; s->result = -1; } /* 返回 = 取消 */ else if (sc) meow_scroll_fling(sc); } } meow_anim_tick(); /* 推进 fling */ if (!s->done) usleep(16000); } meow_ui_modal_end(m); meow_ui_modal_restore(); if (sc) meow_scroll_free(sc); meow_widget_free(lv); return s->result; } void meow_select_free(meow_select *s) { if (s) free(s); } int meow_select_show(const char *title, const char *const *opts, int n, int cur) { if (!opts || n <= 0) return -1; meow_select *s = meow_select_create(title, cur); if (!s) return -1; for (int i = 0; i < n && i < MEOW_SELECT_MAX_ITEMS; i++) meow_select_add_item(s, opts[i]); int r = meow_select_open(s); meow_select_free(s); return r; }