173 lines
7.2 KiB
C
173 lines
7.2 KiB
C
/*
|
|
* meow_ui_widget.c — 控件层(B1.3):按钮/滑条/开关/列表最小集。
|
|
*
|
|
* 控件只负责绘制与状态;命中分发由应用在 on_tap 里调 meow_widget_hit 完成
|
|
* (与 MEOW_APP_SDK.md §5 一致)。绘制读当前主题(meow_theme_get)走 back_buf。
|
|
*/
|
|
#include "meow_ui.h"
|
|
#include "meow_ui_internal.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 };
|
|
|
|
/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。
|
|
* 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */
|
|
#define MEOW_BTN_HOLD_MS 500u
|
|
|
|
/* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。
|
|
* 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */
|
|
static uint32_t blend_color(uint32_t c, uint32_t o, int t)
|
|
{
|
|
int rr = (((c >> 16) & 0xFF) * (100 - t) + ((o >> 16) & 0xFF) * t) / 100;
|
|
int gg = (((c >> 8) & 0xFF) * (100 - t) + ((o >> 8) & 0xFF) * t) / 100;
|
|
int bb = ((c & 0xFF) * (100 - t) + (o & 0xFF) * t) / 100;
|
|
return ((uint32_t)rr << 16) | ((uint32_t)gg << 8) | (uint32_t)bb;
|
|
}
|
|
|
|
struct meow_widget {
|
|
int type;
|
|
int x, y, w, h;
|
|
char label[128];
|
|
int pressed; /* 按钮按下视觉态 */
|
|
int min, max, val; /* 滑条 */
|
|
int on; /* 开关 */
|
|
int count, sel; /* 列表 */
|
|
meow_list_source src;
|
|
void *ctx;
|
|
};
|
|
|
|
meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label)
|
|
{
|
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
|
if (!t) return NULL;
|
|
t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h;
|
|
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
|
|
return t;
|
|
}
|
|
|
|
meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val)
|
|
{
|
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
|
if (!t) return NULL;
|
|
t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40;
|
|
t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val;
|
|
return t;
|
|
}
|
|
|
|
meow_widget *meow_switch_create(int x, int y, int on)
|
|
{
|
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
|
if (!t) return NULL;
|
|
t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32;
|
|
t->on = on ? 1 : 0;
|
|
return t;
|
|
}
|
|
|
|
meow_widget *meow_list_create(int x, int y, int w, int h, int count,
|
|
meow_list_source src, void *ctx)
|
|
{
|
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
|
if (!t) return NULL;
|
|
t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h;
|
|
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
|
|
return t;
|
|
}
|
|
|
|
void meow_widget_free(meow_widget *w) { if (w) free(w); }
|
|
|
|
static int hit(meow_widget *w, int x, int y)
|
|
{
|
|
if (!w) return 0;
|
|
return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h;
|
|
}
|
|
|
|
int meow_widget_hit(meow_widget *w, int x, int y) { return hit(w, x, y); }
|
|
int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; }
|
|
|
|
void meow_slider_set(meow_widget *w, int val)
|
|
{
|
|
if (!w || w->type != W_SLIDER) return;
|
|
if (val < w->min) val = w->min; if (val > w->max) val = w->max;
|
|
w->val = val;
|
|
}
|
|
int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; }
|
|
void meow_switch_set(meow_widget *w, int on)
|
|
{ if (w && w->type == W_SWITCH) w->on = on ? 1 : 0; }
|
|
int meow_switch_get(meow_widget *w) { return w && w->type == W_SWITCH ? w->on : 0; }
|
|
|
|
/* 供应用在 on_tap 里对外可控回馈:这里不存命中态(由 app 分发),仅暴露绘制 */
|
|
void meow_widget_draw(meow_widget *w)
|
|
{
|
|
if (!w) return;
|
|
const meow_theme *th = meow_theme_get();
|
|
switch (w->type) {
|
|
case W_BTN: {
|
|
/* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路:
|
|
* - w->pressed:应用在 on_tap 命中后标记(既有语义,保留);
|
|
* - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。
|
|
* 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。 */
|
|
int live = meow_input_touch_down() &&
|
|
meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w &&
|
|
meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h;
|
|
int press = w->pressed || live;
|
|
int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
|
|
if (hold) {
|
|
/* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调;
|
|
* 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧,
|
|
* 非本「加视觉态」范围),不做 1-2 帧插值。 */
|
|
uint32_t hbg = blend_color(th->accent, 0x000000, 24);
|
|
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg);
|
|
if (w->w > 6 && w->h > 6)
|
|
meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9,
|
|
blend_color(th->accent, 0xFFFFFF, 45));
|
|
} else if (press) {
|
|
/* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */
|
|
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10,
|
|
blend_color(th->accent, 0x000000, 12));
|
|
} else {
|
|
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent);
|
|
}
|
|
meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2,
|
|
w->label, th->font_body, 0xFFFFFF);
|
|
break;
|
|
}
|
|
case W_SLIDER: {
|
|
int trace_y = w->y + w->h / 2 - 3;
|
|
meow_ui_fill_rect(w->x, trace_y, w->w, 6, th->dim);
|
|
int range = w->max - w->min;
|
|
int frac = range ? (w->val - w->min) * w->w / range : 0;
|
|
if (frac > 0) meow_ui_fill_rect(w->x, trace_y, frac, 6, th->accent);
|
|
int tx = w->x + frac;
|
|
int ty = w->y + w->h / 2;
|
|
meow_gfx_fill_round(tx - 10, ty - 10, 20, 20, 10, th->fg);
|
|
break;
|
|
}
|
|
case W_SWITCH: {
|
|
uint32_t bg = w->on ? th->accent : th->dim;
|
|
meow_gfx_fill_round(w->x, w->y, w->w, w->h, w->h / 2, bg);
|
|
int knob_r = w->h / 2 - 3;
|
|
int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3;
|
|
meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF);
|
|
break;
|
|
}
|
|
case W_LIST: {
|
|
int rowh = 44, pad = 16;
|
|
for (int i = 0; i < w->count; i++) {
|
|
int ry = w->y + i * rowh;
|
|
if (ry >= w->y + w->h) break;
|
|
if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->accent);
|
|
char label[64] = "";
|
|
if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) {
|
|
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->fg;
|
|
meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2,
|
|
label, th->font_body, col);
|
|
}
|
|
meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->dim);
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|