Files
StellarX/src/Dialog.cpp

725 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Dialog.h"
Dialog::Dialog(Window& h,std::string text,std::string message, StellarX::MessageBoxType type, bool modal)
: Canvas(),message(message), type(type), modal(modal), hWnd(h), titleText(text)
{
this->id = "Dialog";
show = false;
}
Dialog::~Dialog()
{
}
void Dialog::draw()
{
if(!show)
{
// 如果对话框不可见且需要清理,执行清理
if (pendingCleanup && !isCleaning)
{
performDelayedCleanup();
}
return;
}
// 如果需要初始化,则执行初始化
if (needsInitialization && show)
{
initDialogSize();
needsInitialization = false;
}
if (dirty && show)
{
// 保存当前绘图状态
saveStyle();
Canvas::setBorderColor(this->borderColor);
Canvas::setLinewidth(BorderWidth);
Canvas::setCanvasBkColor(this->backgroundColor);
Canvas::setShape(StellarX::ControlShape::ROUND_RECTANGLE);
Canvas::draw();
//绘制消息文本
settextcolor(textStyle.color);
//设置字体样式
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
for (auto& line:lines)
{
int tx = this->x + ((this->width - textwidth(line.c_str())) / 2); // 文本起始X坐标
outtextxy(tx, ty, LPCTSTR(line.c_str()));
ty = ty + textheight(LPCTSTR(line.c_str())) + 5; // 每行文本高度加5像素间距
}
// 恢复绘图状态
restoreStyle();
dirty = false;
}
}
bool Dialog::handleEvent(const ExMessage& msg)
{
bool consume = false;
if (!show)
{
if (pendingCleanup && !isCleaning)
{
performDelayedCleanup();
}
return false;
}
// 如果正在清理或标记为待清理,则不处理事件
if (pendingCleanup || isCleaning)
return false;
// 模态对话框:点击对话框外部区域时,发出提示音(\a)并吞噬该事件,不允许操作背景内容。
if (modal && msg.message == WM_LBUTTONUP &&
(msg.x < x || msg.x > x + width || msg.y < y || msg.y > y + height))
{
std::cout << "\a" << std::endl;
// 模态对话框不允许点击外部区域
return true;
}
// 将事件传递给子控件处理
if (!consume)
consume = Canvas::handleEvent(msg);
// 每次事件处理后检查是否需要执行延迟清理
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
return consume;
}
void Dialog::SetTitle(const std::string& title)
{
this->titleText = title;
if (this->title)
{
this->title->setText(title);
}
dirty = true;
}
void Dialog::SetMessage(const std::string& message)
{
this->message = message;
splitMessageLines();
getTextSize();
dirty = true;
}
void Dialog::SetType(StellarX::MessageBoxType type)
{
this->type = type;
// 重新初始化按钮
initButtons();
dirty = true;
}
void Dialog::SetModal(bool modal)
{
this->modal = modal;
}
void Dialog::SetResult(StellarX::MessageBoxResult result)
{
this->result = result;
}
StellarX::MessageBoxResult Dialog::GetResult() const
{
return this->result;
}
bool Dialog::model() const
{
return modal;
}
void Dialog::Show()
{
if (pendingCleanup)
performDelayedCleanup();
show = true;
dirty = true;
needsInitialization = true;
close = false;
shouldClose = false;
if (modal)
{
// 模态对话框需要阻塞当前线程直到对话框关闭
if (modal)
{
// 记录当前窗口客户区尺寸,供轮询对比
RECT rc0;
GetClientRect(hWnd.getHwnd(), &rc0);
int lastW = rc0.right - rc0.left;
int lastH = rc0.bottom - rc0.top;
while (show && !close)
{
// ① 轮询窗口尺寸(不依赖 WM_SIZE
RECT rc;
GetClientRect(hWnd.getHwnd(), &rc);
const int cw = rc.right - rc.left;
const int ch = rc.bottom - rc.top;
if (cw != lastW || ch != lastH)
{
lastW = cw;
lastH = ch;
// 通知父窗口:有新尺寸 → 标记 needResizeDirty
hWnd.scheduleResizeFromModal(cw, ch);
// 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态)
hWnd.pumpResizeIfNeeded();
// 这只模态在新尺寸下重建布局 / 重抓背景 → 本帧要画自己
setInitialization(true);
setDirty(true);
}
// ② 处理这只对话框的鼠标/键盘(沿用你原来 EX_MOUSE | EX_KEY
ExMessage msg;
if (peekmessage(&msg, EX_MOUSE | EX_KEY))
{
handleEvent(msg);
if (shouldClose)
{
Close();
break;
}
}
// ③ 最后一笔:只画这只模态,保证永远在最上层
if (dirty)
{
BeginBatchDraw();
this->draw(); // 注意:不要 requestRepaint(parent),只画自己
EndBatchDraw();
dirty = false;
}
Sleep(10);
}
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
}
else
{
// 非模态仍由主循环托管
dirty = true;
}
// 模态对话框关闭后执行清理
if (pendingCleanup && !isCleaning)
performDelayedCleanup();
}
else
// 非模态对话框只需标记为可见,由主循环处理
dirty = true;
}
void Dialog::Close()
{
if (!show) return;
show = false;
close = true;
dirty = true;
pendingCleanup = true; // 只标记需要清理,不立即执行
// 工厂模式下非模态触发回调 返回结果
if (resultCallback&& !modal)
resultCallback(this->result);
}
void Dialog::setInitialization(bool init)
{
if (init)
{
initDialogSize();
saveBackground((x - BorderWidth), (y - BorderWidth), (width + 2 * BorderWidth), (height + 2 * BorderWidth));
this->dirty = true;
}
}
void Dialog::initButtons()
{
controls.clear();
switch (this->type)
{
case StellarX::MessageBoxType::OK: // 只有确定按钮
{
auto okbutton = createDialogButton((this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定"
);
okbutton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::OK);
this->Close(); });
okbutton->textStyle = this->textStyle;
this->addControl(std::move(okbutton));
}
break;
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
{
auto okButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"确定"
);
okButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::OK);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
(okButton.get()->getX() + okButton.get()->getButtonWidth() + buttonMargin),
okButton.get()->getY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
okButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(okButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::YesNo: // 是和否按钮
{
auto yesButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
""
);
yesButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true;
this->Close(); });
auto noButton = createDialogButton(
(yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin),
yesButton.get()->getY(),
""
);
noButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true;
this->Close(); });
yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle;
this->addControl(std::move(yesButton));
this->addControl(std::move(noButton));
}
break;
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
{
auto yesButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
""
);
yesButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Yes);
this->hWnd.dialogClose = true;
this->Close(); });
auto noButton = createDialogButton(
yesButton.get()->getX() + yesButton.get()->getButtonWidth() + buttonMargin,
yesButton.get()->getY(),
""
);
noButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::No);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
noButton.get()->getX() + noButton.get()->getButtonWidth() + buttonMargin,
noButton.get()->getY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
yesButton->textStyle = this->textStyle;
noButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(yesButton));
this->addControl(std::move(noButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
{
auto retryButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin * (buttonNum - 1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"重试"
);
retryButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true;
this->Close(); });
auto cancelButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
retryButton.get()->getY(),
"取消"
);
cancelButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
retryButton->textStyle = this->textStyle;
cancelButton->textStyle = this->textStyle;
this->addControl(std::move(retryButton));
this->addControl(std::move(cancelButton));
}
break;
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
{
auto abortButton = createDialogButton(
(this->x + (this->width - (functionButtonWidth * buttonNum + buttonMargin* (buttonNum-1))) / 2),
((this->y + (this->height - buttonAreaHeight)) + (buttonAreaHeight - functionButtonHeight) / 2),
"中止"
);
abortButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Abort);
this->hWnd.dialogClose = true;
this->Close();
});
auto retryButton = createDialogButton(
abortButton.get()->getX() + abortButton.get()->getButtonWidth() + buttonMargin,
abortButton.get()->getY(),
"重试"
);
retryButton->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Retry);
this->hWnd.dialogClose = true;
this->Close();
});
auto ignoreButton = createDialogButton(
retryButton.get()->getX() + retryButton.get()->getButtonWidth() + buttonMargin,
retryButton.get()->getY(),
"忽略"
);
ignoreButton.get()->setOnClickListener([this]()
{
this->SetResult(StellarX::MessageBoxResult::Ignore);
this->hWnd.dialogClose = true;
this->Close();
});
abortButton->textStyle = this->textStyle;
retryButton->textStyle = this->textStyle;
ignoreButton->textStyle = this->textStyle;
this->addControl(std::move(abortButton));
this->addControl(std::move(retryButton));
this->addControl(std::move(ignoreButton));
}
break;
}
}
void Dialog::initCloseButton()
{
//初始化关闭按钮
auto but = std::make_unique<Button>
(
(this->x + this->width - closeButtonWidth) - 3, (this->y+3), closeButtonWidth-1, closeButtonHeight,
"X", // 按钮文本
RGB(255, 0, 0), // 按钮被点击颜色
this->canvasBkClor, // 按钮背景颜色
RGB(255, 0, 0), // 按钮被悬停颜色
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::B_RECTANGLE
);
but.get()->setButtonFalseColor(this->backgroundColor);
but.get()->enableTooltip(false);
but->setOnClickListener([this]() {
this->SetResult(StellarX::MessageBoxResult::Cancel);
this->hWnd.dialogClose = true;
this->Close(); });
this->closeButton = but.get();
this->addControl(std::move(but));
}
void Dialog::initTitle()
{
this->title = std::make_unique<Label>(this->x+5,this->y+5,titleText,textStyle.color);
title->setTextdisap(true);
title->textStyle = this->textStyle;
this->addControl(std::move(title));
}
void Dialog::splitMessageLines()
{
lines.clear(); // 先清空现有的行
std::string currentLine;
for (size_t i = 0; i < message.length(); i++) {
// 处理 换行符 \r\n \n \r
if (i + 1 < message.length() && (message[i] == '\r' || message[i] == '\n')||(message[i] == '\r' && message[i+1] == '\n'))
{
if (!currentLine.empty()) {
lines.push_back(currentLine);
currentLine.clear();
}
if (message[i] == '\r' && message[i + 1] == '\n')
i++;
continue;
}
currentLine += message[i];
}
// 添加最后一行(如果有内容)
if (!currentLine.empty())
{
lines.push_back(currentLine);
}
// 如果消息为空,至少添加一个空行
if (lines.empty())
{
lines.push_back("");
}
}
void Dialog::getTextSize()
{
saveStyle();
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
for (auto& text : lines)
{
int w = textwidth(LPCTSTR(text.c_str()));
int h = textheight(LPCTSTR(text.c_str()));
if (this->textHeight < h)
this->textHeight = h;
if (this->textWidth < w)
this->textWidth = w;
}
restoreStyle();
}
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
void Dialog::initDialogSize()
{
splitMessageLines(); // 分割消息行
getTextSize(); // 获取文本最大尺寸
// 获取功能按钮数量
switch (this->type)
{
case StellarX::MessageBoxType::OK: // 只有确定按钮
buttonNum = 1;
break;
case StellarX::MessageBoxType::OKCancel: // 确定和取消按钮
case StellarX::MessageBoxType::YesNo: // 是和否按钮
case StellarX::MessageBoxType::RetryCancel: // 重试和取消按钮
buttonNum = 2;
break;
case StellarX::MessageBoxType::YesNoCancel: // 是、否和取消按钮
case StellarX::MessageBoxType::AbortRetryIgnore: // 中止、重试和忽略按钮
buttonNum = 3;
break;
}
// 计算按钮区域宽度
int buttonAreaWidth = buttonNum * functionButtonWidth +
(buttonNum > 0 ? (buttonNum +1) * buttonMargin : 0);
// 计算文本区域宽度(包括边距)
int textAreaWidth = textWidth + textToBorderMargin * 2 ;
// 对话框宽度取两者中的较大值,并确保最小宽度
this->width = buttonAreaWidth > textAreaWidth ? buttonAreaWidth : textAreaWidth;
this->width = this->width > 200 ? this->width : 200;
// 计算对话框高度
// 高度 = 标题栏高度 + 文本区域高度 + 按钮区域高度 + 间距
int textAreaHeight = textHeight * (int)lines.size() + 5*((int)lines.size()-1); // 文本行高+行间距
this->height = closeButtonHeight + // 标题栏高度
titleToTextMargin + // 标题到文本的间距
textAreaHeight + // 文本区域高度
buttonAreaHeight; // 按钮区域高度
// 居中定位对话框
this->x = (hWnd.getWidth() - this->width) / 2;
this->y = (hWnd.getHeight() - this->height) / 2;
//this->textStyle.nWidth = 10;
this->textStyle.nHeight = 20;
initButtons(); // 初始化按钮
initTitle(); // 初始化标题标签
initCloseButton(); // 初始化关闭按钮
}
void Dialog::addControl(std::unique_ptr<Control> control)
{
control->setParent(this);
controls.push_back(std::move(control));
dirty = true;
}
// 延迟清理策略:由于对话框绘制时保存了背景快照,必须在对话框隐藏后、
// 所有控件析构前恢复背景,否则会导致背景图像被错误覆盖。
// 此方法在对话框不可见且被标记为待清理时由 draw() 或 handleEvent() 调用。
void Dialog::performDelayedCleanup()
{
if (isCleaning) return;
isCleaning = true;
auto& c = hWnd.getControls();
for (auto& control : c)
control->setDirty(true);
controls.clear();
// 重置指针
closeButton = nullptr;
title.reset();
// 释放背景图像资源
if (saveBkImage && hasSnap)
{
restBackground();
FlushBatchDraw();
discardBackground();
}
if (!(saveBkImage && hasSnap))
{
// 没有背景快照:强制一次完整重绘,立即擦掉残影
hWnd.pumpResizeIfNeeded(); // 如果正好有尺寸标志,顺便统一收口
// 即使没有尺寸变化,也重绘一帧
BeginBatchDraw();
// 背景
if (hWnd.getBkImage() && !hWnd.getBkImageFile().empty())
putimage(0, 0, hWnd.getBkImage());
else { setbkcolor(hWnd.getBkcolor()); cleardevice(); }
// 所有普通控件
for (auto& c : hWnd.getControls()) c->draw();
// 其他对话框this 已经 show=false会早退不绘
// 注意:此处若有容器管理,需要按你的现状遍历 dialogs 再 draw
EndBatchDraw();
FlushBatchDraw();
}
// 重置状态
needsInitialization = true;
pendingCleanup = false;
isCleaning = false;
shouldClose = false;
}
void Dialog::SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb)
{
resultCallback = std::move(cb);
}
std::string Dialog::GetCaption() const
{
return titleText;
}
std::string Dialog::GetText() const
{
return message;
}
void Dialog::clearControls()
{
controls.clear();
// 重置按钮指针
closeButton = nullptr;
title.reset(); // 释放标题资源
}
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
{
auto btn = std::make_unique<Button>(
x, y, functionButtonWidth, functionButtonHeight,
text,
buttonTrueColor, // 点击色
buttonFalseColor, // 背景色
buttonHoverColor, // 悬停色
StellarX::ButtonMode::NORMAL,
StellarX::ControlShape::RECTANGLE
);
return btn;
}
void Dialog::requestRepaint(Control* parent)
{
if (this == parent)
{
for (auto& control : controls)
if (control->isDirty() && control->IsVisible())
control->draw();
}
else
onRequestRepaintAsRoot();
}