Chapter 130 · XUI 框架架构

事件系统

XUI 事件系统采用三相位分发(捕获→目标→冒泡),支持事件掩码过滤与 64 位事件类型。

130

概念

XUI 事件系统类似 DOM 事件模型,分三个相位:
• 捕获相位:事件从根向下传播到目标控件
• 目标相位:到达目标控件,触发其处理器
• 冒泡相位:事件从目标向上回传到根
每个控件通过 EventMask(64 位位掩码)声明关注哪些事件类型, 未注册的事件不会分发到该控件,节省性能。

事件类型枚举

枚举值说明
XUI_EVENT_NONE (0)无事件
XUI_EVENT_POINTER_ENTER (1)指针进入
XUI_EVENT_POINTER_LEAVE (2)指针离开
XUI_EVENT_POINTER_MOVE (3)指针移动
XUI_EVENT_POINTER_DOWN (4)指针按下
XUI_EVENT_POINTER_UP (5)指针抬起
XUI_EVENT_POINTER_CLICK (6)点击(按下+抬起)
XUI_EVENT_POINTER_WHEEL (7)滚轮
XUI_EVENT_KEY_DOWN (8)按键按下
XUI_EVENT_KEY_UP (9)按键抬起
XUI_EVENT_TEXT (10)文本输入
XUI_EVENT_FOCUS (11)获得焦点
XUI_EVENT_BLUR (12)失去焦点
XUI_EVENT_VIEWPORT (13)视口变化
XUI_EVENT_DPI (14)DPI 变化

API 速查

函数/宏说明
XUI_EVENT_MASK(e)事件→掩码位 (1ULL << e)
uint64_t xuiWidgetGetSubtreeEventMask(xui_widget pW)子树掩码
uint64_t xuiWidgetGetEventMask(xui_widget pW)获取事件掩码
int xuiWidgetSetEventHandler(xui_widget pW, xui_widget_event_proc pFn, void* pUser)注册事件处理器
回调返回 XUI_EVENT_DISPATCH_STOP阻止传播
默认行为由具体控件的命令处理决定
int xuiInputPointerDown(xui_context pCtx, float fX, float fY, int iBtn, uint32_t iBtns)注入指针按下
int xuiInputPointerUp(xui_context pCtx, float fX, float fY, int iBtn, uint32_t iBtns)注入指针抬起
int xuiInputPointerMove(xui_context pCtx, float fX, float fY, uint32_t iBtns)注入指针移动
int xuiInputPointerWheel(xui_context pCtx, float fX, float fY, float fWX, float fWY, uint32_t iBtns)注入滚轮

编译本章示例

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

示例:按钮点击 + 冒泡拦截

// 事件处理器回调
static int OnButtonEvent(xui_widget pWidget,
    xui_event_t* pEvent, void* pUser)
{
    if (pEvent->iType == XUI_EVENT_POINTER_CLICK) {
        printf("Button clicked!\n");
        // 阻止事件继续冒泡到父容器
        return XUI_EVENT_DISPATCH_STOP;
    }
    return XUI_OK;
}

// 注册:只关注 CLICK 和 HOVER
/* 事件掩码可读(xuiWidgetGetEventMask),过滤在回调内判断 */
xuiWidgetSetEventHandler(pBtn, OnButtonEvent, NULL);

// 父容器拦截:捕获相位处理
static int OnPanelEvent(xui_widget pW,
    xui_event_t* pEv, void* pUser)
{
    if (pEv->iPhase == XUI_EVENT_PHASE_CAPTURE &&
        pEv->iType == XUI_EVENT_KEY_DOWN) {
        // 面板级快捷键处理
        return XUI_EVENT_DISPATCH_STOP;
    }
    return XUI_OK;
}

// 主循环注入输入
xuiInputPointerMove(pCtx, mx, my, 0);
xuiInputPointerDown(pCtx, mx, my, 0, 1);
xuiInputPointerUp(pCtx, mx, my, 0, 0);

完整代码

将以下代码保存为 ch130_main.c,编译后运行即可看到事件分发与冒泡拦截:

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

static xui_context g_pCtx;

static int OnBtnEvent(xui_widget pW,
    xui_event_t* pEv, void* pUser) {
    (void)pW; (void)pUser;
    if (pEv->iType == XUI_EVENT_POINTER_CLICK) {
        printf("Button clicked!\n");
        return XUI_EVENT_DISPATCH_STOP;
    }
    return XUI_OK;
}

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);

    // 注入鼠标输入到 XUI
    float mx, my;
    xgeMouseGet(&mx, &my);
    xuiInputPointerMove(g_pCtx, mx, my, 0);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, mx, my, 0, 1);
    if (!xgeMouseDown(XGE_MOUSE_LEFT) /* 释放边沿建议改用事件 */)
        xuiInputPointerUp(g_pCtx, mx, my, 0, 0);

    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  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch130 - Event System";
    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, 800, 600);

    // 创建按钮并注册事件
    xui_widget pBtn;
    xuiWidgetCreate(g_pCtx, &pBtn);
    xuiWidgetSetRect(pBtn,
        (xui_rect_t){ 300, 260, 200, 60 });
    /* 事件掩码可读(xuiWidgetGetEventMask),过滤在回调内判断 */
    xuiWidgetSetEventHandler(pBtn,
        OnBtnEvent, NULL);

    xgeRun(onFrame, NULL);

    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • xuiWidgetGetEventMask:读取控件关注的事件掩码,未登记的事件不会分发。
  • xuiWidgetSetEventCallback:注册事件回调,事件到达时触发。
  • XUI_EVENT_DISPATCH_STOP:回调返回此值阻止事件继续传播。
  • xuiInputPointer*:将平台鼠标输入注入 XUI 事件系统。
  • 三相位:CAPTURE → TARGET → BUBBLE,可在任意相位拦截。

要点与陷阱

  • EventMask:未设置掩码的控件不接收任何事件(性能优化)。
  • 三相位:CAPTURE → TARGET → BUBBLE,可在任意相位拦截。
  • StopPropagation:阻止事件继续传播(捕获或冒泡方向)。
  • PreventDefault:阻止控件默认行为(如按钮的按下态切换)。
  • 输入注入:通过 xuiInput* 系列将平台输入转发给 XUI。
  • 64 位掩码:最多支持 64 种事件类型,当前使用 0-14。

进阶练习

  1. 实现事件日志:在根控件捕获相位打印所有事件流向。
  2. 模态对话框:在捕获相位拦截所有 POINTER 事件,只允许对话框内交互。
事件系统效果
图:事件系统渲染效果