Chapter 190 · XUI 游戏与交互控件

Canvas 画布控件

自定义绘制画布:即时模式绘图 API、Surface 绘制、Mesh 三角形、滚动/缩放。

190

概念

Canvas 是 XUI 中的自定义绘制控件:
• 即时模式:每帧调用 Draw* 函数绘制图元
• 图元:Point/Line/Triangle/Rect/Circle/Surface/Mesh
• 画布尺寸:独立于控件大小,支持滚动/偏移
• Surface:纹理贴图绘制(Quad/自定义顶点)
适用:自定义图表、游戏地图、绘图工具、可视化编辑器。

API 速查

函数说明
int xuiCanvasCreate(pCtx, ppW, pDesc)创建画布
int xuiCanvasSetCanvasSize(pW, fW, fH)画布尺寸
int xuiCanvasClear(pW, iColor)清屏
int xuiCanvasDrawLine(pW, fX0, fY0, fX1, fY1, fW, iColor)线段
int xuiCanvasDrawRectFill(pW, tRect, iColor)填充矩形
int xuiCanvasDrawCircleFill(pW, fX, fY, fR, iColor)填充圆
int xuiCanvasDrawSurface(pW, pSurf, tSrc, tDst, iColor, iFlags)纹理
int xuiCanvasDrawMeshTriangles(pW, pVerts, iVC, pIdx, iIC, iFlags)Mesh
int xuiCanvasSetOffset(pW, fX, fY)滚动偏移
int xuiCanvasEnsureRectVisible(pW, tRect)滚动到可见

编译本章示例

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

示例:自定义绘图

xui_canvas_desc_t desc = { 0 };

xui_widget pCanvas;
xuiCanvasCreate(pCtx, &pCanvas, &desc);

// 画布 2000×2000(可滚动)
xuiCanvasSetCanvasSize(pCanvas, 2000.0f, 2000.0f);

// 每帧绘制(在 Render 回调中)
xuiCanvasClear(pCanvas, 0x1A1A2EFF);

// 网格线
for (int i = 0; i <= 2000; i += 50) {
    xuiCanvasDrawLine(pCanvas, i, 0, i, 2000, 1.0f, 0x33335540);
    xuiCanvasDrawLine(pCanvas, 0, i, 2000, i, 1.0f, 0x33335540);
}

// 绘制精灵
xui_rect_t tSrc = { 0, 0, 64, 64 };
xui_rect_t tDst = { 100, 100, 64, 64 };
xuiCanvasDrawSurface(pCanvas, pSprite, tSrc, tDst, 0xFFFFFFFF, 0);

// 高亮圆
xuiCanvasDrawCircleFill(pCanvas, 400.0f, 300.0f, 20.0f, 0xFF6B5E80);

完整代码

将以下代码保存为 ch190_main.c,编译后运行即可看到 Canvas 自定义绘图效果:

#include "xge.h"
#include "xui.h"

static xui_context g_pCtx;
static xui_widget g_pCanvas;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1A1A2EFF);
    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);

    // 每帧重绘画布
    xuiCanvasClear(g_pCanvas, 0x1A1A2EFF);

    // 网格线
    for (int i = 0; i <= 800; i += 40) {
        xuiCanvasDrawLine(g_pCanvas, (float)i, 0, (float)i, 600, 1.0f, 0x33335540);
        xuiCanvasDrawLine(g_pCanvas, 0, (float)i, 800, (float)i, 1.0f, 0x33335540);
    }

    // 填充圆
    xuiCanvasDrawCircleFill(g_pCanvas, 200.0f, 150.0f, 40.0f, 0xFF6B5E80);

    // 填充矩形
    xui_rect_t rc = { 300, 200, 120, 80 };
    xuiCanvasDrawRectFill(g_pCanvas, rc, 0x4FD8C260);

    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 = "ch190 - Canvas";
    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_canvas_desc_t cdesc = { 0 };
    xuiCanvasCreate(g_pCtx, &g_pCanvas, &cdesc);
    xuiCanvasSetCanvasSize(g_pCanvas, 800.0f, 600.0f);

    xui_widget pRoot = xuiGetRootWidget(g_pCtx);
    xuiWidgetAddChild(pRoot, g_pCanvas);
    xuiWidgetSetRect(g_pCanvas, (xui_rect_t){0, 0, 800, 600});

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

代码讲解

  • CanvasCreate:创建画布控件,支持即时模式绘图。
  • SetCanvasSize:画布可大于控件尺寸,配合 Offset 滚动。
  • 每帧重绘:即时模式,先 Clear 再绘制所有图元。
  • DrawLine/DrawCircleFill/DrawRectFill:基础图元绘制 API。

要点与陷阱

  • 即时模式:每帧重绘,不保留绘制状态(与 retained mode 不同)。
  • CanvasSize:画布可大于控件,通过 Offset/ScrollBy 滚动。
  • 性能:大量图元时考虑 Mesh 批量提交减少 Draw Call。
  • 坐标:绘制坐标为画布坐标,非控件本地坐标。

进阶练习

  1. 绘图板:鼠标拖拽绘制自由线条(Line 连续段)。
  2. 小地图:Canvas 绘制游戏世界缩略图 + 玩家位置。
Canvas 画布控件效果
图:Canvas 画布控件渲染效果