模态框 Modal
弹出一个对话框,挡住后面的界面,让用户必须处理完才能继续——这就是模态框。保存确认、设置面板、警告提示,全都靠它。
HiEasyX 的 Modal 不是阻塞线程的(毕竟我们每帧都要跑 peekmessage),而是通过 Focus Stack 在消息层"挡住"背景输入。背景上的按钮不会因为被模态框盖住而误触,同时你还能看到背后的界面(通常带一层半透明暗色遮罩)。
函数原型
// 打开指定 ID 的模态框
void OpenModal(const HXString &Id);
// 关闭指定 ID 的模态框
void CloseModal(const HXString &Id);
// 查询某模态框当前是否处于打开状态
bool IsModalOpen(const HXString &Id);
// 开始绘制一个模态框。返回 true 表示该模态框正在显示,应在 if 块内放内容
bool BeginModal(const HXString &Id, ModalProfile &Profile);
// 结束模态框内容区域
void EndModal();
参数详解
ModalProfile
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Size | HXPoint | {400, 300} | 模态框的宽和高 |
Title | HXString | — | 标题栏文字 |
CloseOnClickOutside | bool | true | 点击模态框外部区域是否自动关闭 |
CloseButton | bool | true | 标题栏右上角是否显示关闭按钮 |
BackdropColor | HXColor | {0, 0, 0, 115} | 背景遮罩颜色,默认是半透明黑 |
TitleBarHeight | int | 36 | 标题栏高度 |
CloseButtonSize | int | 32 | 关闭按钮的尺寸 |
你不需要手动画遮罩!在 HX::Render() 阶段,框架会自动为所有打开的模态框绘制 BackdropColor 遮罩层。你只要专注于在 BeginModal/EndModal 之间放控件就行。
返回值
| 函数 | 返回值 | 说明 |
|---|---|---|
IsModalOpen | bool | 该 ID 的模态框是否处于打开状态 |
BeginModal | bool | true 表示模态框正在显示,应当在 if 块内绘制内容。false 时跳过即可 |
最小示例:确认对话框
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void ConfirmDeleteDemo() {
static HX::ModalProfile mp{}; // 必须是 static 或全局
mp.Size = {360, 180};
mp.Title = HXStr("确认删除");
mp.CloseOnClickOutside = true;
mp.CloseButton = true;
// ---- 触发按钮 ----
if (HX::Button(HXStr("删除选中项"), HX::ButtonProfile{})) {
HX::OpenModal(HXStr("confirm_delete"));
}
// ---- 模态框内容 ----
if (HX::BeginModal(HXStr("confirm_delete"), mp)) {
HX::Text(HXStr("确定要永久删除这个文件吗?"));
HX::Text(HXStr("此操作无法撤销。"));
HX::Spacing();
HX::BeginHorizontal();
if (HX::Button(HXStr("取消"), HX::ButtonProfile{})) {
HX::CloseModal(HXStr("confirm_delete"));
}
HX::SameLine();
if (HX::Button(HXStr("确定删除"), HX::ButtonProfile{})) {
// 执行删除逻辑...
HX::CloseModal(HXStr("confirm_delete"));
}
HX::EndHorizontal();
HX::EndModal();
}
}
阻塞背景输入的原理
Modal 打开时,背景上的所有控件都不会收到鼠标消息。这是通过 Focus Stack 实现的:
BeginModal内部会PushFocusScope。- 只有位于 Focus Stack 最顶层 的作用域才能消费"区域外"的消息。
- 如果点击发生在模态框外部,
ConsumeMessagesOutsideRect会把消息标记为Processed,背景控件就"看不见"这次点击了。
你可以同时打开多个 Modal(比如主 Modal 里再弹一个子设置框)。Focus Stack 保证只有最上面那个能接收外部点击关闭事件,下面的 modal 不会意外被关掉。这就是"层级"的真正含义。
完整示例:设置面板 + 嵌套确认
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
void SettingsApp() {
// ========== 主窗口内容 ==========
HX::WindowProfile wp{};
wp.Size = {800, 600};
HX::Window(HXStr("MainApp"), wp);
HX::Text(HXStr("主界面——背景按钮"));
HX::Button(HXStr("打开设置"), HX::ButtonProfile{});
// 点击打开设置
if (HX::Button(HXStr("⚙ 设置"), HX::ButtonProfile{})) {
HX::OpenModal(HXStr("settings"));
}
// ========== 设置模态框 ==========
static HX::ModalProfile settingsMp{};
settingsMp.Size = {500, 400};
settingsMp.Title = HXStr("应用设置");
settingsMp.CloseOnClickOutside = true;
if (HX::BeginModal(HXStr("settings"), settingsMp)) {
HX::Text(HXStr("通用"));
static bool autoSave = true;
HX::Checkbox(HXStr("自动保存"), HX::CheckboxProfile{autoSave});
HX::Separator();
HX::Text(HXStr("编辑器"));
static float fontSize = 16.0f;
HX::Slider1f(HXStr("字体大小"), fontSize, HX::SliderProfile1f{});
HX::Separator();
HX::Text(HXStr("危险操作"));
if (HX::Button(HXStr("恢复默认设置"), HX::ButtonProfile{})) {
HX::OpenModal(HXStr("confirm_reset"));
}
HX::EndModal();
}
// ========== 嵌套确认模态框 ==========
static HX::ModalProfile confirmMp{};
confirmMp.Size = {320, 160};
confirmMp.Title = HXStr("确认重置");
confirmMp.CloseOnClickOutside = false; // 这个必须点按钮,不能随便点外面关
if (HX::BeginModal(HXStr("confirm_reset"), confirmMp)) {
HX::Text(HXStr("所有自定义设置将被清除!"));
HX::Text(HXStr("确定要继续吗?"));
HX::BeginHorizontal();
if (HX::Button(HXStr("取消"), HX::ButtonProfile{})) {
HX::CloseModal(HXStr("confirm_reset"));
}
HX::SameLine();
if (HX::Button(HXStr("确定重置"), HX::ButtonProfile{})) {
autoSave = true;
fontSize = 16.0f;
HX::CloseModal(HXStr("confirm_reset"));
HX::CloseModal(HXStr("settings")); // 连设置面板一起关掉
}
HX::EndHorizontal();
HX::EndModal();
}
}
常见坑与注意事项
BeginModal 返回 bool。只有返回 true 时才代表这个模态框处于打开状态。如果你不在 if 块里写内容,模态框打开时里面就是空的;如果跳过 if 判断直接画,关闭后那些控件会泄露到主窗口上。
// ❌ 错误:没有 if 判断
HX::BeginModal(HXStr("x"), mp);
HX::Text(HXStr("这会在关闭后仍然显示!"));
HX::EndModal();
// ✅ 正确
if (HX::BeginModal(HXStr("x"), mp)) {
HX::Text(HXStr("安全地放在模态框里"));
HX::EndModal();
}
ModalProfile 会被框架内部用来存储动画状态、拖拽偏移等信息。如果放在栈上,每帧重置会导致各种奇怪行为。
即使 CloseOnClickOutside = true,模态框内部的按钮仍然可以正常点击。框架会精确判断点击是落在模态框内容区内还是区外,不会误伤。
除了点外部和点关闭按钮,你也可以在任何逻辑里调用 CloseModal:比如用户点了"保存"、按了 ESC、或者后台任务完成后自动关闭进度弹窗。
运行效果
截图占位符:请补充 $name 的运行效果截图。
截图占位符:请补充
模态框 Modal 运行效果的运行效果截图,保存为./assets/模态框 Modal_view.png。`n