Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_widget.c
T
MeowOS 6824defdf9 M2.60→M2.61 骨架: SDK Activity 栈 + 控件归属(agent-122) + 按钮去圈/即时重绘(agent-119) + SDK 模板文档(agent-121)
- SDK §6.1 三层控件模型: Activity栈(push/pop/active) + 页面控件归属 owner + meow_ui_global_* 骨架(②) + 系统级仅内部接口(③)
- meow_widget 加 owner; live_btn_hit/meow_widget_hit 仅 active activity 命中; touch_refresh stale 保护
- 根治"全屏页触摸主页按钮闪现"(跨页面控件残留); 控件对象复用弹栈不重建
- hello 迁移双 Activity(home/full): 全屏左滑=弹栈回主页, 根左滑=退出
- agent-119 按钮去圈(纯色accent+黑12/24%)+即时局部重绘保留; agent-121 SDK模板§5.3-5.7
2026-08-30 15:27:10 +08:00

275 lines
11 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_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;
int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */
char label[128];
int pressed; /* 按钮按下视觉态 */
int min, max, val; /* 滑条 */
int on; /* 开关 */
int count, sel; /* 列表 */
meow_list_source src;
void *ctx;
};
/* ---- 实时按压即时刷新(agent-119)----
* 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在
* on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/
* 按住达阈值时主动重绘命中的按钮区域并局部上屏(不等 app 重绘),即时反映按下变深/
* 释放恢复,连续快速点击每按都反映。仅针对 SDK 控件按钮(W_BTN);app 纯自绘不适用。
* 语义不变:on_tap / meow_btn_pressed / meow_widget_hit 零改动;非按钮区域不动。 */
#define MEOW_LIVE_BTN_MAX 64
static meow_widget *g_live_btns[MEOW_LIVE_BTN_MAX];
static int g_live_btn_n = 0;
static meow_widget *g_live_btn = NULL; /* 实时按压中最上层按钮(无 = NULL) */
static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住态 */
static void live_btn_register(meow_widget *w)
{
if (!w || w->type != W_BTN) return;
for (int i = 0; i < g_live_btn_n; i++)
if (g_live_btns[i] == w) return;
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w;
}
static void live_btn_unregister(meow_widget *w)
{
for (int i = 0; i < g_live_btn_n; i++) {
if (g_live_btns[i] == w) {
g_live_btns[i] = g_live_btns[--g_live_btn_n];
if (g_live_btn == w) g_live_btn = NULL;
return;
}
}
if (g_live_btn == w) g_live_btn = NULL;
}
/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */
static meow_widget *live_btn_hit(int x, int y)
{
int active = meow_ui_activity_active();
meow_widget *r = NULL;
for (int i = 0; i < g_live_btn_n; i++) {
meow_widget *w = g_live_btns[i];
if (w->owner != active) continue; /* 非 active activity:跳过 */
if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h)
r = w; /* 越后创建越上:保留最后命中 */
}
return r;
}
/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */
static void live_btn_redraw(meow_widget *w)
{
if (!w) return;
meow_widget_draw(w);
meow_ui_flush_region(w->x, w->y, w->w, w->h);
}
/* 由 meow_ui_run 每帧调用(主线程):检测实时触摸状态迁移,即时重绘命中按钮的按压/按住/
* 释放,不等 app on_tick/on_tap。仅在触摸状态相对上次渲染发生变化时动作(非全屏、省性能)。 */
void meow_widget_touch_refresh(void)
{
int active = meow_ui_activity_active();
/* §6.1 ①:若当前按住按钮已不属于 active activity(按压期间发生了 Activity 切换,
* 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上
* 画出旧按钮的 accent 残留)。 */
if (g_live_btn && g_live_btn->owner != active) {
g_live_btn = NULL;
g_live_prev_hold = 0;
}
int down = meow_input_touch_down();
meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL;
int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
if (!down) {
/* 释放:把最近按压按钮恢复为常态 */
if (g_live_btn) { live_btn_redraw(g_live_btn); g_live_btn = NULL; }
g_live_prev_hold = 0;
return;
}
if (hit != g_live_btn) {
/* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */
if (g_live_btn) live_btn_redraw(g_live_btn);
g_live_btn = hit;
g_live_prev_hold = hold;
if (hit) live_btn_redraw(hit);
} else if (hit && hold != g_live_prev_hold) {
/* 同一按钮按住达阈值:升级到更强态 */
live_btn_redraw(hit);
g_live_prev_hold = hold;
}
}
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;
t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
live_btn_register(t);
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->owner = meow_ui_activity_active();
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->owner = meow_ui_activity_active();
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->owner = meow_ui_activity_active();
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
return t;
}
void meow_widget_free(meow_widget *w)
{
if (!w) return;
live_btn_unregister(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)
{
if (!w) return 0;
/* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */
if (w->owner != meow_ui_activity_active()) return 0;
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;
/* 纯色状态层(无描边环、无分层):一层即满,深浅二态均由 accent 向黑调出。
* 常态=accent、按下=accent+黑12%、按住=accent+黑24%。整体一层不「先底色再套圈」。 */
uint32_t bg;
if (hold)
bg = blend_color(th->accent, 0x000000, 24);
else if (press)
bg = blend_color(th->accent, 0x000000, 12);
else
bg = th->accent;
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg);
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;
}
}