弹出菜单 PopupMenu
右键点击呼出菜单、下拉列表、工具按钮旁边的快捷操作——
PopupMenu是 HiEasyX 里的"悬浮菜单"解决方案。它不占用固定布局空间,点哪弹哪,点完即走。
和 Modal 不同,PopupMenu 更轻量,通常用于一次性命令选择。它返回一个整数索引告诉你用户点了第几项,然后自动关闭(或者你手动关)。
函数原型
// 在屏幕指定位置打开一个弹出菜单
void OpenPopupMenu(PopupMenuProfile &Profile, HXPoint ScreenPos);
// 手动关闭弹出菜单
void ClosePopupMenu(PopupMenuProfile &Profile);
// 查询某个菜单是否正打开
bool IsPopupMenuOpen(PopupMenuProfile &Profile);
// 查询当前是否有任意弹出菜单处于打开状态
bool IsAnyPopupMenuOpen();
// 渲染并处理弹出菜单交互。返回被点击项的索引,-1 表示未点击
HXGInt PopupMenu(PopupMenuProfile &Profile);
参数详解
PopupMenuProfile
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Position | HXPoint | {0, 0} | 菜单左上角屏幕坐标(通常由 OpenPopupMenu 自动填充) |
Open | bool | false | 菜单是否处于打开状态(内部维护) |
Items | std::vector<PopupMenuItem> | — | 菜单项列表 |
Width | int | 180 | 菜单整体宽度 |
ItemHeight | int | 28 | 普通项的高度 |
SeparatorHeight | int | 8 | 分隔线的高度 |
Padding | int | 8 | 菜单内边距 |
FontSize | int | 16 | 文字大小 |
BackgroundColor | HXColor | 继承主题 | 菜单背景色 |
BorderColor | HXColor | 继承主题 | 菜单边框色 |
TextColor | HXColor | 继承主题 | 文字颜色 |
PopupMenuItem
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Label | HXString | — | 显示文字 |
Enabled | bool | true | 是否可点击。false 时文字变灰且不响应鼠标 |
Separator | bool | false | 如果为 true,这一项渲染为分隔线,Label 被忽略 |
返回值
| 函数 | 返回值 | 说明 |
|---|---|---|
PopupMenu | HXGInt(int) | 被点击项在 Items 数组中的索引。如果本帧没有点击任何项,返回 -1 |
IsPopupMenuOpen | bool | 该 Profile 对应的菜单是否正打开 |
IsAnyPopupMenuOpen | bool | 全局是否有任意菜单打开(常用于阻止背景交互) |
最小示例:右键上下文菜单
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void RightClickMenuDemo() {
static HX::PopupMenuProfile menu{}; // 必须是 static / 全局
menu.Width = 160;
menu.ItemHeight = 26;
menu.FontSize = 14;
// 定义菜单项(每帧可以动态改变)
menu.Items = {
HX::PopupMenuItem{HXStr("复制")},
HX::PopupMenuItem{HXStr("剪切")},
HX::PopupMenuItem{HXStr("粘贴")},
HX::PopupMenuItem{HXStr(""), true, true}, // 分隔线
HX::PopupMenuItem{HXStr("删除"), true},
HX::PopupMenuItem{HXStr("重命名"), true},
HX::PopupMenuItem{HXStr(""), true, true}, // 分隔线
HX::PopupMenuItem{HXStr("属性"), true},
};
// ---- 主界面 ----
HX::Text(HXStr("在下方区域右键点击试试"));
HX::Button(HXStr("我是背景按钮"), HX::ButtonProfile{});
// ---- 检测右键按下,打开菜单 ----
if (HX::Context.MessageQuery.MouseRightPressed) {
HX::OpenPopupMenu(menu, {
HX::Context.MessageQuery.MouseX,
HX::Context.MessageQuery.MouseY
});
}
// ---- 渲染菜单并读取点击结果 ----
HXGInt clicked = HX::PopupMenu(menu);
if (clicked >= 0) {
switch (clicked) {
case 0: /* 复制 */ break;
case 1: /* 剪切 */ break;
case 2: /* 粘贴 */ break;
case 4: /* 删除 */ break;
case 5: /* 重命名 */ break;
case 7: /* 属性 */ break;
}
// 菜单不会自动关闭所有情况,但点击后通常应该关掉
HX::ClosePopupMenu(menu);
}
}
分隔线与禁用项
menu.Items = {
HX::PopupMenuItem{HXStr("撤销"), false}, // 灰色,点不动
HX::PopupMenuItem{HXStr("重做"), false},
HX::PopupMenuItem{HXStr(""), true, true}, // 分隔线
HX::PopupMenuItem{HXStr("复制")},
HX::PopupMenuItem{HXStr(""), true, true}, // 再来一条分隔线
HX::PopupMenuItem{HXStr("删除"), true},
};
Separator = true时,那一项会变成一条细横线,不占用ItemHeight而是占用SeparatorHeight。Enabled = false时,文字颜色会变暗,鼠标悬停不显示高亮,点击也不会返回索引。
与 Blueprint 内置右键菜单的区别
| 特性 | HX::PopupMenu | Blueprint 画布右键菜单 |
|---|---|---|
| 使用方式 | 显式调用 OpenPopupMenu + PopupMenu | Blueprint 内部自动触发,设置 ContextMenuRequested 标志 |
| 数据源 | 你手动构造 PopupMenuItem 数组 | Blueprint 内部根据节点注册表自动生成节点分类列表 |
| 返回值 | 项索引 int | 无直接返回值,通过创建节点回调实现 |
| 定位 | 任意屏幕坐标 | 固定为鼠标右键位置 |
| 适用场景 | 通用上下文菜单、工具栏下拉 | 蓝图节点编辑器专用 |
一句话区分
PopupMenu 是通用菜单组件,哪里都能用;Blueprint 的右键菜单是蓝图专属逻辑,基于同一个底层渲染,但你不需要(也不能)直接调用 PopupMenu 去渲染它。
完整示例:带图标的文件列表 + 右键菜单
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
#include <vector>
void FileBrowserWithContextMenu() {
struct FileItem {
HXString Name;
bool IsFolder;
bool Selected;
};
static std::vector<FileItem> files = {
{HXStr("src"), true, false},
{HXStr("assets"), true, false},
{HXStr("main.cpp"), false, false},
{HXStr("logo.png"), false, false},
{HXStr("README.md"), false, false},
};
// ---- 绘制文件列表 ----
HX::Text(HXStr("项目文件"));
for (size_t i = 0; i < files.size(); ++i) {
HX::BeginHorizontal();
HX::Text(files[i].IsFolder ? HXStr("📁 ") : HXStr("📄 "));
HX::SameLine();
HX::ButtonProfile bp{};
if (files[i].Selected) {
bp.BackgroundColor = HX::GetTheme().SelectionBackground;
}
if (HX::Button(files[i].Name, bp)) {
// 左键选择
for (auto &f : files) f.Selected = false;
files[i].Selected = true;
}
HX::EndHorizontal();
}
// ---- 右键菜单 Profile ----
static HX::PopupMenuProfile ctxMenu{};
ctxMenu.Width = 150;
ctxMenu.FontSize = 14;
static size_t rightClickedIndex = 0;
// 检测右键:记录当前项并打开菜单
if (HX::Context.MessageQuery.MouseRightPressed) {
// 简单演示:假设总是对第一个选中项操作
ctxMenu.Items = {
HX::PopupMenuItem{HXStr("打开")},
HX::PopupMenuItem{HXStr("在资源管理器中显示")},
HX::PopupMenuItem{HXStr(""), true, true},
HX::PopupMenuItem{HXStr("复制路径")},
HX::PopupMenuItem{HXStr("重命名")},
HX::PopupMenuItem{HXStr(""), true, true},
HX::PopupMenuItem{HXStr("删除"), true},
};
HX::OpenPopupMenu(ctxMenu, {
HX::Context.MessageQuery.MouseX,
HX::Context.MessageQuery.MouseY
});
}
// ---- 渲染并处理菜单 ----
HXGInt choice = HX::PopupMenu(ctxMenu);
if (choice >= 0) {
switch (choice) {
case 0: /* 打开 */ break;
case 1: /* 在资源管理器中显示 */ break;
case 3: /* 复制路径 */ break;
case 4: /* 重命名 */ break;
case 6: /* 删除 */ break;
}
HX::ClosePopupMenu(ctxMenu);
}
}
常见坑与注意事项
Profile 必须是 static / 全局
PopupMenuProfile 内部维护了 Open 状态、悬停索引、动画计时器等跨帧数据。栈上临时对象会导致菜单闪烁或无法保持打开。
点击后记得处理返回值
PopupMenu 返回索引后不会自动关闭菜单(某些版本行为可能不同,建议显式处理)。安全做法是在 choice >= 0 分支里执行完逻辑后调用 ClosePopupMenu。
多个菜单用不同的 Profile
如果你同时需要"文件右键菜单"和"画布右键菜单",定义两个独立的 static PopupMenuProfile,分别传给各自的 PopupMenu()。不要复用同一个 Profile,否则状态会打架。
用 IsAnyPopupMenuOpen 阻止背景操作
if (!HX::IsAnyPopupMenuOpen()) {
// 只有没有菜单打开时才处理背景快捷键
if (HX::IsKeyPressed('S')) { /* Ctrl+S 保存 */ }
}
运行效果
截图占位符:请补充 $name 的运行效果截图。
截图占位符:请补充
弹出菜单 PopupMenu 运行效果的运行效果截图,保存为./assets/弹出菜单 PopupMenu_view.png。`n