跳到主要内容

模态框 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

成员类型默认值说明
SizeHXPoint{400, 300}模态框的宽和高
TitleHXString标题栏文字
CloseOnClickOutsidebooltrue点击模态框外部区域是否自动关闭
CloseButtonbooltrue标题栏右上角是否显示关闭按钮
BackdropColorHXColor{0, 0, 0, 115}背景遮罩颜色,默认是半透明黑
TitleBarHeightint36标题栏高度
CloseButtonSizeint32关闭按钮的尺寸
背景遮罩怎么渲染?

你不需要手动画遮罩!在 HX::Render() 阶段,框架会自动为所有打开的模态框绘制 BackdropColor 遮罩层。你只要专注于在 BeginModal/EndModal 之间放控件就行。


返回值

函数返回值说明
IsModalOpenbool该 ID 的模态框是否处于打开状态
BeginModalbooltrue 表示模态框正在显示,应当在 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 实现的:

  1. BeginModal 内部会 PushFocusScope
  2. 只有位于 Focus Stack 最顶层 的作用域才能消费"区域外"的消息。
  3. 如果点击发生在模态框外部,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();
}
}

常见坑与注意事项

必须在 if (BeginModal) 里放内容

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();
}
Profile 必须是 static / 全局

ModalProfile 会被框架内部用来存储动画状态、拖拽偏移等信息。如果放在栈上,每帧重置会导致各种奇怪行为。

CloseOnClickOutside 与按钮共存

即使 CloseOnClickOutside = true,模态框内部的按钮仍然可以正常点击。框架会精确判断点击是落在模态框内容区内还是区外,不会误伤。

手动控制关闭时机

除了点外部和点关闭按钮,你也可以在任何逻辑里调用 CloseModal:比如用户点了"保存"、按了 ESC、或者后台任务完成后自动关闭进度弹窗。

运行效果

截图占位符:请补充 $name 的运行效果截图。

截图占位符:请补充 模态框 Modal 运行效果 的运行效果截图,保存为 ./assets/模态框 Modal_view.png。`n