Chapter 154 · XUI 弹出层与反馈

Popup 弹出层

Popup 通用弹出容器,支持锚点定位、方向翻转、模态/非模态、关闭策略、焦点管理。

154

概念

Popup 是所有弹出式 UI 的基础容器(Menu、ComboBox 下拉均基于它):
• Anchor:锚点矩形 + 锚点位置(弹出相对于锚点的对齐方式)
• Direction:弹出方向(上/下/左/右),空间不足时自动翻转
• ClosePolicy:点击外部/点击 Owner/按 Esc 时是否关闭
• Modal:模态模式显示半透明遮罩
• ContentSize / MaxSize:内容尺寸与最大限制

API 速查

函数说明
int xuiPopupCreate(xui_context pCtx, xui_widget* ppW, const xui_popup_desc_t* pDesc)创建弹出层
int xuiPopupSetOpen(xui_widget pW, int bOpen)打开/关闭
int xuiPopupSetAnchorRect(xui_widget pW, xui_rect_t tAnchor)锚点矩形
int xuiPopupSetDirection(xui_widget pW, int iDirection)弹出方向
int xuiPopupSetClosePolicy(xui_widget pW, int iOutside, int iOwner, int iEsc)关闭策略
int xuiPopupSetModal(xui_widget pW, int bModal)模态
int xuiPopupSetContentSize(xui_widget pW, float fW, float fH)内容尺寸
int xuiPopupSetMatchOwnerWidth(xui_widget pW, int bEnabled)匹配 Owner 宽度
xui_widget xuiPopupGetContentWidget(xui_widget pW)获取内容容器
int xuiPopupSetFocusPolicy(xui_widget pW, int iPolicy, xui_widget pCustom)焦点策略

编译本章示例

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

示例:下拉面板

xui_popup_desc_t desc = { 0 };

xui_widget pPopup;
xuiPopupCreate(pCtx, &pPopup, &desc);

// 锚定到按钮下方
xuiPopupSetOwner(pPopup, pButton);
xuiPopupSetDirection(pPopup, 1); // 向下
xuiPopupSetMatchOwnerWidth(pPopup, 1);
xuiPopupSetGap(pPopup, 4.0f);

// 关闭策略:点击外部关闭,Esc 关闭
xuiPopupSetClosePolicy(pPopup, 1, 1, 1);

// 添加内容
xui_widget pContent = xuiPopupGetContentWidget(pPopup);
xuiWidgetAddChild(pContent, pListView);

// 打开
xuiPopupSetOpen(pPopup, 1);

完整代码

将以下代码保存为 ch154_main.c,编译后运行即可看到弹出层效果:

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

static xui_context g_pCtx;
static xui_widget g_pPopup;
static int g_bOpen = 0;

static void onBtnClick(xui_widget pW, void* p) {
    (void)pW; (void)p;
    g_bOpen = !g_bOpen;
    xuiPopupSetOpen(g_pPopup, g_bOpen);
}

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);
    float mx, my;
    xgeMouseGet(&mx, &my);
    xuiInputPointerMove(g_pCtx, mx, my);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, 0);
    else
        xuiInputPointerUp(g_pCtx, 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 = "ch154 - Popup";
    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 pRoot;
    xuiWidgetCreate(g_pCtx, &pRoot);
xuiWidgetSetLayoutType(pRoot, XUI_LAYOUT_COLUMN);
    xuiWidgetSetRect(pRoot, (xui_rect_t){0, 0, 800, 600});
    xuiWidgetSetPadding(pRoot, 40, 40, 40, 40);
    xuiSetRootWidget(g_pCtx, pRoot);

    /* 触发按钮 */
    xui_button_desc_t btn = { 0 };
    btn.sText = "打开菜单";
    xui_widget pBtn;
    xuiButtonCreate(g_pCtx, &pBtn, &btn);
    xuiWidgetSetRect(pBtn, (xui_rect_t){0, 0, 150, 36});
    xuiButtonSetClick(pBtn, onBtnClick, NULL);
    xuiWidgetAddChild(pRoot, pBtn);

    /* 创建弹出层 */
    xui_popup_desc_t pd = { 0 };
    xuiPopupCreate(g_pCtx, &g_pPopup, &pd);
    xuiPopupSetOwner(g_pPopup, pBtn);
    xuiPopupSetDirection(g_pPopup, 1);
    xuiPopupSetMatchOwnerWidth(g_pPopup, 1);
    xuiPopupSetClosePolicy(g_pPopup, 1, 1, 1);
    xuiPopupSetContentSize(g_pPopup, 150, 120);

    /* 弹出层内容 */
    xui_widget pContent = xuiPopupGetContentWidget(g_pPopup);
    xui_label_desc_t lb = { 0 };
    lb.sText = "菜单项 1\n菜单项 2\n菜单项 3";
    xui_widget pLb;
    xuiLabelCreate(g_pCtx, &pLb, &lb);
    xuiWidgetAddChild(pContent, pLb);

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

代码讲解

  • SetOwner:将弹出层锚定到按钮,自动计算位置。
  • Direction=1:向下弹出,空间不足时自动翻转。
  • ClosePolicy:点击外部/点击 Owner/按 Esc 均关闭。
  • GetContentWidget:获取内容容器,向其中添加菜单项。

要点与陷阱

  • ApplyPlacement:打开后自动计算位置,空间不足时翻转方向。
  • Margin:弹出层与屏幕边缘的最小间距。
  • FocusPolicy:打开时焦点转移到弹出层内,关闭后恢复。
  • ConsumeInside:启用后弹出层内的点击不穿透到下层。
  • ScrollbarStyle:内容超出 MaxSize 时显示滚动条。

进阶练习

  1. 自定义下拉选择器:Popup + ListView 实现 ComboBox 效果。
  2. 右键上下文菜单:在鼠标位置设置 AnchorRect 后打开。
Popup 弹出层效果
图:Popup 弹出层渲染效果