Chapter 116 · 输入系统

鼠标输入

MouseGet 获取位置、MouseGetDelta 获取帧间位移、MouseGetWheel 获取滚轮、MouseDown 检测按键。

116

概念

XGE 鼠标 API 提供三类信息:
• 位置:MouseGet 获取当前光标坐标(窗口坐标系)
• 位移:MouseGetDelta 获取帧间相对移动(FPS 视角)
• 滚轮:MouseGetWheel 获取滚轮增量
按键检测复用 xgeMouseDown(左键=1,右键=2,中键=3)。

API 速查

函数说明
void xgeMouseGet(float* pX, float* pY)获取光标位置
void xgeMouseGetDelta(float* pDX, float* pDY)获取帧间位移
void xgeMouseGetWheel(float* pX, float* pY)获取滚轮增量
int xgeMouseDown(int iButton)鼠标按键是否按下

编译本章示例

gcc -O2 -I. -o ch116.exe ch116_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch116.exe

示例:拖拽 + 滚轮缩放

static int dragging = 0;
static float camX = 0, camY = 0, zoom = 1.0f;

void onFrame(void) {
    float mx, my;
    xgeMouseGet(&mx, &my);

    // 左键拖拽平移
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        dragging = 1;
    if (!xgeMouseDown(XGE_MOUSE_LEFT) /* 释放边沿建议改用事件 */)
        dragging = 0;

    if (dragging) {
        float dx, dy;
        xgeMouseGetDelta(&dx, &dy);
        camX -= dx / zoom;
        camY -= dy / zoom;
    }

    // 滚轮缩放
    float wx, wy;
    xgeMouseGetWheel(&wx, &wy);
    if (wy != 0) {
        zoom *= (wy > 0) ? 1.1f : 0.9f;
        if (zoom < 0.1f) zoom = 0.1f;
        if (zoom > 10.0f) zoom = 10.0f;
    }

    // 右键菜单
    if (xgeKeyPressed(XGE_MOUSE_RIGHT)) {
        showContextMenu(mx, my);
    }
}

完整代码

将以下代码保存为 ch116_main.c,编译后运行即可看到鼠标拖拽平移 + 滚轮缩放效果:

#include "xge.h"
#include <math.h>

static xge_font g_font = NULL;
static float g_camX = 0.0f, g_camY = 0.0f;
static float g_zoom = 1.0f;
static int g_dragging = 0;

static int onFrame(void* pUser) {
    (void)pUser;
    float mx, my;
    xgeMouseGet(&mx, &my);

    // 左键拖拽平移
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        g_dragging = 1;
    if (!xgeMouseDown(XGE_MOUSE_LEFT) /* 释放边沿建议改用事件 */)
        g_dragging = 0;
    if (g_dragging) {
        float dx, dy;
        xgeMouseGetDelta(&dx, &dy);
        g_camX -= dx / g_zoom;
        g_camY -= dy / g_zoom;
    }

    // 滚轮缩放
    float wx, wy;
    xgeMouseGetWheel(&wx, &wy);
    if (wy != 0) {
        g_zoom *= (wy > 0) ? 1.1f : 0.9f;
        if (g_zoom < 0.2f) g_zoom = 0.2f;
        if (g_zoom > 5.0f) g_zoom = 5.0f;
    }

    // 绘制网格背景
    xgeClear(0x0A1A1FFF);
    float step = 50.0f * g_zoom;
    float offX = fmodf(-g_camX * g_zoom, step);
    float offY = fmodf(-g_camY * g_zoom, step);
    for (float x = offX; x < 800; x += step)
        xgeShapeLine(x, 0, x, 600, 0x1A3A3FFF, 1);
    for (float y = offY; y < 600; y += step)
        xgeShapeLine(0, y, 800, y, 0x1A3A3FFF, 1);

    // 绘制光标位置指示
    xgeShapeCircleFill(mx, my, 6,
        g_dragging ? 0xFF6B5EFF : 0x4FD8C2FF);

    // HUD
    char buf[128];
    sprintf(buf, "Mouse(%.0f,%.0f) Zoom:%.2f Cam(%.0f,%.0f)",
        mx, my, g_zoom, g_camX, g_camY);
    xgeTextDraw(g_font, buf, 20, 20, 0xFFFFFFFF);
    xgeTextDraw(g_font, "LMB:drag | Wheel:zoom",
        20, 55, 0xAAAAAAFF);

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch116 - Mouse";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);

    g_font = xgeFontLoad("res/font/default.ttf", 24);
    xgeRun(onFrame, NULL);

    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • xgeMouseGet:获取光标当前窗口坐标。
  • xgeMouseGetDelta:帧间位移,拖拽平移用(与光标绝对位置无关)。
  • xgeMouseGetWheel:滚轮增量,每帧累积后自动归零。
  • xgeKeyPressed(MOUSE1):鼠标左键按下检测(复用键盘 API)。
  • 网格背景:根据相机偏移和缩放绘制无限网格。

要点与陷阱

  • 坐标系:MouseGet 返回窗口坐标(左上角为原点)。
  • Delta:帧间位移,适合 FPS 视角旋转(与光标位置无关)。
  • Wheel:每帧累积值,帧末自动归零。
  • 按键码:MOUSE1=左,MOUSE2=右,MOUSE3=中。

进阶练习

  1. 实现画布编辑器:拖拽平移 + 滚轮缩放 + 框选。
  2. FPS 视角:隐藏光标,Delta 控制 yaw/pitch。

已验证实现

权威源码为 examples/tutorial_capture/ch116_main1.c。它回放移动、左键按下、滚轮和左键抬起四个有序事件,读取 fX、fY、fDX、fDY 与 iButton 后断言最终位置、滚轮增量和按键状态。真实鼠标与触控板同样通过该事件模型进入控件。

XGE pointer event replay with final pointer position and wheel delta
图:真实运行的指针事件回放。蓝线表示移动轨迹,橙点为抬起时位置;底栏展示被断言的最终坐标和滚轮增量。