Chapter 198 · XUI 高级机制

命中测试

xuiHitTest:坐标 → 控件查找、标志位控制、层优先级、穿透与裁剪。

198

概念

命中测试(HitTest)将屏幕坐标映射到控件:
• xuiHitTest:给定 (x, y) 返回最上层命中控件
• 层优先:从最高层(DEBUG)向最低层(NORMAL)搜索
• 标志位:控制是否穿透透明区域、是否包含禁用控件
• 裁剪:被父控件裁剪的区域不命中
• 用途:拖拽 Drop 判定、右键菜单定位、调试拾取

API 速查

函数说明
xui_widget xuiHitTest(pCtx, fX, fY, iFlags)坐标命中测试

编译本章示例

gcc -O2 -I. -o ch198.exe ch198_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch198.exe

示例:右键菜单定位

// 鼠标右键按下时
void OnRightClick(float fX, float fY) {
    // 命中测试:找到鼠标下的控件
    xui_widget pHit = xuiHitTest(pCtx,
        fX, fY, 0);

    if (pHit) {
        // 根据控件类型弹出不同菜单
        int iType = xuiWidgetGetType(pHit);
        if (iType == xuiListViewGetType(pCtx)) {
            ShowListContextMenu(pHit, fX, fY);
        } else if (iType ==
                   xuiCanvasGetType(pCtx)) {
            ShowCanvasContextMenu(pHit, fX, fY);
        }
    }
}

// 拖拽 Drop 判定
void OnDragEnd(float fX, float fY) {
    xui_widget pTarget = xuiHitTest(pCtx,
        fX, fY, 0);
    if (pTarget && CanAcceptDrop(pTarget)) {
        PerformDrop(pTarget);
    }
}

完整代码

将以下代码保存为 ch198_main.c,编译后运行即可看到命中测试与控件拾取效果:

#include "xge.h"
#include "xui.h"
#include <stdio.h>

static xui_context g_pCtx;
static xui_widget g_pInfo;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1a1a2e);
    xui_vec2_t pos;
    xgeMouseGet(&pos.x, &pos.y);
    xuiInputPointerMove(g_pCtx, pos.x, pos.y);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, pos.x, pos.y, 0);
    else
        xuiInputPointerUp(g_pCtx, pos.x, pos.y, 0);

    /* 实时命中测试 */
    xui_widget pHit = xuiHitTest(g_pCtx, pos.x, pos.y, 0);
    char buf[128];
    sprintf(buf, "Hit: %s", pHit ? "widget" : "none");
    xuiLabelSetText(g_pInfo, buf);

    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth = 640; d.iHeight = 480;
    d.sTitle = "ch198 - HitTest";
    d.iFlags = XGE_INIT_VSYNC;
    xgeInit(&d);
    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 640, 480);

    xui_widget pRoot;
xuiPanelCreate(g_pCtx, &pRoot, NULL);
    xuiWidgetSetRect(pRoot, (xui_rect_t){0, 0, 640, 480});

    xui_widget pBtn;
xuiButtonCreate(g_pCtx, &pBtn, NULL);
xuiWidgetAddChild(pRoot, pBtn);
    xuiLabelSetText(pBtn, "Target");
    xuiWidgetSetRect(pBtn, (xui_rect_t){240, 140, 160, 48});
    xuiWidgetSetRect(pBtn, (xui_rect_t){0, 0, 160, 48});

    g_pInfo = xuiLabelCreate(g_pCtx, &g_pInfo, NULL);
xuiWidgetAddChild(pRoot, g_pInfo);
    xuiLabelSetText(g_pInfo, "Move mouse over button");
    xuiWidgetSetRect(g_pInfo, (xui_rect_t){200, 240, 240, 32});
    xuiWidgetSetRect(g_pInfo, (xui_rect_t){0, 0, 240, 32});

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • HitTest:每帧用鼠标坐标调用 xuiHitTest,返回最上层命中控件。
  • 用途:右键菜单定位、拖拽 Drop 判定、调试拾取。
  • 层顺序:MODAL 层控件会阻挡下层命中,返回最前层控件。
  • 性能:遍历控件树,复杂 UI 中避免每帧调用,可降频或事件触发。

要点与陷阱

  • 返回值:返回最上层命中控件,NULL 表示无命中。
  • 透明穿透:默认透明像素不命中,标志可控制。
  • 层顺序:MODAL 层控件会阻挡下层命中。
  • 性能:HitTest 遍历控件树,复杂 UI 中避免每帧调用。

进阶练习

  1. 控件拾取器:DEBUG 层显示鼠标下控件名称/类型/尺寸。
  2. 穿透点击:工具栏区域点击穿透到下层画布。
命中测试效果
图:命中测试渲染效果