Save pre-batched-redraw snapshot
This commit is contained in:
+11
-9
@@ -276,9 +276,11 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show)
|
||||
return false;
|
||||
resetEventVisualChanged();
|
||||
|
||||
bool oldHover = hover;// 注意:只在状态变化时记录,避免 WM_MOUSEMOVE 刷屏
|
||||
bool oldClick = click;
|
||||
const bool oldTipVisible = tipVisible;
|
||||
|
||||
bool consume = false;//是否消耗事件
|
||||
const bool isMouseMessage =
|
||||
@@ -312,12 +314,6 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hover != oldHover)
|
||||
{
|
||||
SX_LOG_TRACE("Button") << SX_T("悬停变化: ","hover change: ") << "id=" << id
|
||||
<< " text= " << text
|
||||
<< " " << (oldHover ? 1 : 0) << "->" << (hover ? 1 : 0);
|
||||
}
|
||||
// 处理鼠标点击事件
|
||||
if (msg.message == WM_LBUTTONDOWN && hover && mode != StellarX::ButtonMode::DISABLED)
|
||||
{
|
||||
@@ -396,8 +392,6 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
// 到点就显示
|
||||
if (GetTickCount64() - tipHoverTick >= (ULONGLONG)tipDelayMs)
|
||||
{
|
||||
SX_LOG_TRACE("Button") << SX_T("提示信息显示: ","tooltip show:")<<" id = " << id <<SX_T("延时时间: ", " delayMs = ") << tipDelayMs;
|
||||
|
||||
tipVisible = true;
|
||||
|
||||
// 定位(跟随鼠标 or 相对按钮)
|
||||
@@ -424,8 +418,16 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
}
|
||||
|
||||
// 如果状态发生变化,标记需要重绘
|
||||
if (hover != oldHover || click != oldClick)
|
||||
const bool stateChanged = (hover != oldHover || click != oldClick);
|
||||
if (stateChanged)
|
||||
dirty = true;
|
||||
const bool tipVisibilityChanged = (tipVisible != oldTipVisible);
|
||||
|
||||
// 鼠标命中按钮区域,或按钮自身状态因此发生变化时,吞掉该事件。
|
||||
// 这样可以避免被遮挡/重叠的下层控件继续收到同一事件并把自己重绘到上层。
|
||||
if (isMouseMessage && (hover || oldHover || click != oldClick))
|
||||
consume = true;
|
||||
markEventVisualChanged(stateChanged || tipVisibilityChanged);
|
||||
|
||||
// 如果需要重绘,立即执行
|
||||
if (dirty)
|
||||
|
||||
+20
-2
@@ -6,6 +6,19 @@ static bool SxIsNoisyMsg(UINT m)
|
||||
return m == WM_MOUSEMOVE;
|
||||
}
|
||||
|
||||
static const char* SxCanvasMsgName(UINT m)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case WM_MOUSEMOVE: return "WM_MOUSEMOVE";
|
||||
case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN";
|
||||
case WM_LBUTTONUP: return "WM_LBUTTONUP";
|
||||
case WM_KEYDOWN: return "WM_KEYDOWN";
|
||||
case WM_KEYUP: return "WM_KEYUP";
|
||||
default: return "WM_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
Canvas::Canvas()
|
||||
:Control(0, 0, 100, 100)
|
||||
{
|
||||
@@ -113,9 +126,11 @@ void Canvas::draw()
|
||||
bool Canvas::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show) return false;
|
||||
resetEventVisualChanged();
|
||||
|
||||
bool consumed = false;
|
||||
bool anyDirty = false;
|
||||
bool anyVisualChanged = false;
|
||||
Control* firstConsumer = nullptr;
|
||||
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
@@ -124,6 +139,7 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
||||
bool cConsumed = c->handleEvent(msg);
|
||||
|
||||
if (c->isDirty()) anyDirty = true;
|
||||
if (c->didEventAffectVisual()) anyVisualChanged = true;
|
||||
|
||||
if (cConsumed)
|
||||
{
|
||||
@@ -135,8 +151,9 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
||||
|
||||
if (firstConsumer && !SxIsNoisyMsg(msg.message))
|
||||
{
|
||||
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: msg=") << msg.message
|
||||
<< SX_T("子控件"," by child")<<" id=" << firstConsumer->getId();
|
||||
SX_LOGD("Event") << SX_T("Canvas 消耗消息: ","Canvas consumed: ")
|
||||
<< SxCanvasMsgName(msg.message)
|
||||
<< SX_T(" 子控件 id=", " childId=") << firstConsumer->getId();
|
||||
}
|
||||
|
||||
if (anyDirty)
|
||||
@@ -145,6 +162,7 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
||||
SX_LOGD("Dirty") << SX_T("Canvas检测有控件为脏状态 -> 请求重绘, ","Canvas anyDirty -> requestRepaint, ")<<"id = " << id;
|
||||
requestRepaint(parent);
|
||||
}
|
||||
markEventVisualChanged(anyVisualChanged);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ protected:
|
||||
Control* parent = nullptr; // 父控件
|
||||
bool dirty = true; // 是否重绘
|
||||
bool show = true; // 是否显示
|
||||
bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化
|
||||
|
||||
/* == 布局模式 == */
|
||||
StellarX::LayoutMode layoutMode = StellarX::LayoutMode::Fixed; // 布局模式
|
||||
@@ -128,6 +129,7 @@ public:
|
||||
std::string getId() const { return id; }
|
||||
//检查是否为脏
|
||||
bool isDirty() { return dirty; }
|
||||
bool didEventAffectVisual() const { return eventVisualChanged; }
|
||||
//用来检查对话框是否模态,其他控件不用实现
|
||||
virtual bool model()const = 0;
|
||||
//布局
|
||||
@@ -139,4 +141,6 @@ public:
|
||||
protected:
|
||||
void saveStyle();
|
||||
void restoreStyle();
|
||||
void resetEventVisualChanged() { eventVisualChanged = false; }
|
||||
void markEventVisualChanged(bool changed = true) { eventVisualChanged = changed; }
|
||||
};
|
||||
|
||||
+53
-25
@@ -44,12 +44,15 @@ void Dialog::draw()
|
||||
|
||||
//绘制消息文本
|
||||
settextcolor(textStyle.color);
|
||||
setbkmode(TRANSPARENT);
|
||||
|
||||
//设置字体样式
|
||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
||||
|
||||
outtextxy(x + 5, y + 5, LPCTSTR(titleText.c_str()));
|
||||
|
||||
int ty = y + closeButtonHeight + titleToTextMargin; // 文本起始Y坐标
|
||||
for (auto& line : lines)
|
||||
{
|
||||
@@ -68,6 +71,7 @@ void Dialog::draw()
|
||||
bool Dialog::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
bool consume = false;
|
||||
resetEventVisualChanged();
|
||||
if (!show)
|
||||
{
|
||||
if (pendingCleanup && !isCleaning)
|
||||
@@ -80,12 +84,20 @@ bool Dialog::handleEvent(const ExMessage& msg)
|
||||
// 如果正在清理或标记为待清理,则不处理事件
|
||||
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))
|
||||
|
||||
const bool isMouseMessage =
|
||||
msg.message == WM_MOUSEMOVE ||
|
||||
msg.message == WM_LBUTTONDOWN ||
|
||||
msg.message == WM_LBUTTONUP;
|
||||
const bool insideDialog =
|
||||
msg.x >= x && msg.x <= x + width &&
|
||||
msg.y >= y && msg.y <= y + height;
|
||||
|
||||
// 模态对话框区域外鼠标事件不允许落到底层背景。
|
||||
if (modal && isMouseMessage && !insideDialog)
|
||||
{
|
||||
std::cout << "\a" << std::endl;
|
||||
if (msg.message == WM_LBUTTONUP)
|
||||
std::cout << "\a" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -93,6 +105,10 @@ bool Dialog::handleEvent(const ExMessage& msg)
|
||||
if (!consume)
|
||||
consume = Canvas::handleEvent(msg);
|
||||
|
||||
// 对话框矩形范围内的鼠标事件一律由对话框吞掉,避免穿透到底层控件。
|
||||
if (isMouseMessage && insideDialog)
|
||||
consume = true;
|
||||
|
||||
// 每次事件处理后检查是否需要执行延迟清理
|
||||
if (pendingCleanup && !isCleaning)
|
||||
performDelayedCleanup();
|
||||
@@ -182,9 +198,8 @@ void Dialog::Show()
|
||||
// 立即统一收口:父窗重绘 背景+普通控件(不会画到这只模态)
|
||||
hWnd.pumpResizeIfNeeded();
|
||||
|
||||
// 这只模态在新尺寸下重建布局 / 重抓背景 → 本帧要画自己
|
||||
setInitialization(true);
|
||||
setDirty(true);
|
||||
// 这只模态只重新居中,不参与拉伸;背景快照需要在新位置重抓。
|
||||
recenterInHostWindow();
|
||||
}
|
||||
|
||||
// ② 处理这只对话框的鼠标/键盘(沿用原来 EX_MOUSE | EX_KEY)
|
||||
@@ -257,10 +272,30 @@ void Dialog::setInitialization(bool init)
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::recenterInHostWindow()
|
||||
{
|
||||
if (!show)
|
||||
return;
|
||||
|
||||
// 尚未完成首次初始化时,保持“延迟初始化”语义,由首次 draw 统一创建子控件。
|
||||
if (needsInitialization || width <= 0 || height <= 0)
|
||||
{
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const int newX = (hWnd.getWidth() - width) / 2;
|
||||
const int newY = (hWnd.getHeight() - height) / 2;
|
||||
invalidateBackgroundSnapshot();
|
||||
|
||||
x = newX;
|
||||
y = newY;
|
||||
rebuildChrome();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void Dialog::initButtons()
|
||||
{
|
||||
controls.clear();
|
||||
|
||||
switch (this->type)
|
||||
{
|
||||
case StellarX::MessageBoxType::OK: // 只有确定按钮
|
||||
@@ -489,16 +524,6 @@ void Dialog::initCloseButton()
|
||||
this->addControl(std::move(but));
|
||||
}
|
||||
|
||||
void Dialog::initTitle()
|
||||
{
|
||||
auto titleLabel = std::make_unique<Label>(this->x + 5, this->y + 5, titleText, textStyle.color);
|
||||
titleLabel->setTextdisap(true);
|
||||
titleLabel->textStyle = this->textStyle;
|
||||
this->title = titleLabel.get();
|
||||
|
||||
this->addControl(std::move(titleLabel));
|
||||
}
|
||||
|
||||
void Dialog::splitMessageLines()
|
||||
{
|
||||
lines.clear(); // 先清空现有的行
|
||||
@@ -570,6 +595,13 @@ void Dialog::invalidateLayout(bool clearChildren)
|
||||
this->dirty = true;
|
||||
}
|
||||
|
||||
void Dialog::rebuildChrome()
|
||||
{
|
||||
clearControls();
|
||||
initButtons();
|
||||
initCloseButton();
|
||||
}
|
||||
|
||||
// 计算逻辑:对话框宽度取【文本区域最大宽度】和【按钮区域总宽度】中的较大值。
|
||||
// 对话框高度 = 标题栏 + 文本区 + 按钮区 + 各种间距。
|
||||
void Dialog::initDialogSize()
|
||||
@@ -626,9 +658,7 @@ void Dialog::initDialogSize()
|
||||
this->x = (hWnd.getWidth() - this->width) / 2;
|
||||
this->y = (hWnd.getHeight() - this->height) / 2;
|
||||
|
||||
initButtons(); // 初始化按钮
|
||||
initTitle(); // 初始化标题标签
|
||||
initCloseButton(); // 初始化关闭按钮
|
||||
rebuildChrome();
|
||||
}
|
||||
|
||||
void Dialog::addControl(std::unique_ptr<Control> control)
|
||||
@@ -655,7 +685,6 @@ void Dialog::performDelayedCleanup()
|
||||
|
||||
// 重置指针
|
||||
closeButton = nullptr;
|
||||
title = nullptr;
|
||||
// 释放背景图像资源
|
||||
if (saveBkImage && hasSnap)
|
||||
{
|
||||
@@ -709,7 +738,6 @@ void Dialog::clearControls()
|
||||
controls.clear();
|
||||
// 重置按钮指针
|
||||
closeButton = nullptr;
|
||||
title = nullptr; // 释放标题资源
|
||||
}
|
||||
|
||||
std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::string& text)
|
||||
|
||||
@@ -40,7 +40,6 @@ class Dialog : public Canvas
|
||||
|
||||
StellarX::MessageBoxType type = StellarX::MessageBoxType::OK; //对话框类型
|
||||
std::string titleText = "提示"; //标题文本
|
||||
Label* title = nullptr; //标题标签(由 controls 持有)
|
||||
|
||||
std::string message; //提示信息
|
||||
std::vector<std::string> lines; //消息内容按行分割
|
||||
@@ -105,14 +104,14 @@ public:
|
||||
void Close();
|
||||
//初始化
|
||||
void setInitialization(bool init);
|
||||
// 宿主窗口变化时仅重新居中,不拉伸 Dialog 自身
|
||||
void recenterInHostWindow();
|
||||
|
||||
private:
|
||||
// 初始化按钮
|
||||
void initButtons();
|
||||
// 初始化关闭按钮
|
||||
void initCloseButton();
|
||||
// 初始化标题
|
||||
void initTitle();
|
||||
// 按行分割消息内容
|
||||
void splitMessageLines();
|
||||
// 获取文本大小
|
||||
@@ -121,6 +120,8 @@ private:
|
||||
void invalidateLayout(bool clearChildren);
|
||||
//初始化对话框尺寸
|
||||
void initDialogSize();
|
||||
// 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮
|
||||
void rebuildChrome();
|
||||
void addControl(std::unique_ptr<Control> control);
|
||||
|
||||
// 清除所有控件
|
||||
|
||||
@@ -99,6 +99,7 @@ void TextBox::draw()
|
||||
bool TextBox::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show) return false;
|
||||
resetEventVisualChanged();
|
||||
|
||||
bool hover = false;
|
||||
bool oldClick = click;
|
||||
@@ -165,6 +166,7 @@ bool TextBox::handleEvent(const ExMessage& msg)
|
||||
|
||||
if (dirty)
|
||||
requestRepaint(parent);
|
||||
markEventVisualChanged(dirty);
|
||||
|
||||
if (click)
|
||||
click = false;
|
||||
|
||||
+35
-14
@@ -3,6 +3,8 @@
|
||||
#include"SxLog.h"
|
||||
#include <easyx.h>
|
||||
#include <algorithm>
|
||||
// 可能频繁出现且对调试信息干扰较大的消息(例如鼠标移动),
|
||||
// 可以在日志输出时特殊处理以减少干扰。
|
||||
static bool SxIsNoisyMsg(UINT m)
|
||||
{
|
||||
return m == WM_MOUSEMOVE;
|
||||
@@ -425,6 +427,17 @@ int Window::runEventLoop()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasVisibleNonModalDialog = false;
|
||||
for (const auto& d : dialogs)
|
||||
{
|
||||
if (d->IsVisible() && !d->model())
|
||||
{
|
||||
hasVisibleNonModalDialog = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool controlVisualChanged = false;
|
||||
|
||||
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
|
||||
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
|
||||
{
|
||||
@@ -433,7 +446,9 @@ int Window::runEventLoop()
|
||||
consume = d->handleEvent(msg);
|
||||
if (consume)
|
||||
{
|
||||
SX_LOGD("Event") << SX_T("事件被非模态对话框处理","Event consumed by non-modal dialog");
|
||||
if (!SxIsNoisyMsg(msg.message))
|
||||
SX_LOGD("Event") << SX_T("事件被非模态对话框处理:", "Event consumed by non-modal dialog: ")
|
||||
<< SxMsgName(msg.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -441,15 +456,21 @@ int Window::runEventLoop()
|
||||
{
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
{
|
||||
consume = (*it)->handleEvent(msg);
|
||||
Control* current = it->get();
|
||||
consume = current->handleEvent(msg);
|
||||
if (current->didEventAffectVisual())
|
||||
controlVisualChanged = true;
|
||||
if (consume)
|
||||
{
|
||||
SX_LOGD("Event") << SX_T("事件被控件处理 id=", "Event consumed by control id=") << (*it)->getId();
|
||||
redrawDialogs = true; // 控件事件可能引起对话框状态变化,标记需要重绘对话框
|
||||
if (!SxIsNoisyMsg(msg.message))
|
||||
SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
|
||||
<< SxMsgName(msg.message)
|
||||
<< SX_T(" id=", " id=") << current->getId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVisibleNonModalDialog && controlVisualChanged)
|
||||
redrawDialogs = true; // 只有普通控件真的发生视觉变化时,才补画非模态对话框
|
||||
}
|
||||
}
|
||||
// 关键点⑦:事件处理后,如果对话框状态可能变化(例如控件事件引起的控件重绘可能导致对话框被覆盖),优先重绘对话框以确保界面响应及时;后续再根据 needResizeDirty 进行一次性重绘。
|
||||
@@ -550,14 +571,6 @@ int Window::runEventLoop()
|
||||
// 批量通知控件“窗口尺寸变化”,并标记重绘
|
||||
for (auto& c : controls)
|
||||
adaptiveLayout(c, finalH, finalW);
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (auto dd = dynamic_cast<Dialog*>(d.get()))
|
||||
{
|
||||
dd->setDirty(true);
|
||||
dd->setInitialization(true);
|
||||
}
|
||||
}
|
||||
//重绘窗口
|
||||
Resize(nullptr, finalW, finalH);
|
||||
|
||||
@@ -585,6 +598,14 @@ int Window::runEventLoop()
|
||||
// 最终提交“当前已应用尺寸”(用于外部查询/下次比较)
|
||||
width = renderWidth;
|
||||
height = renderHeight;
|
||||
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (auto dd = dynamic_cast<Dialog*>(d.get()))
|
||||
{
|
||||
dd->recenterInHostWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统一批量绘制
|
||||
@@ -782,7 +803,7 @@ void Window::pumpResizeIfNeeded()
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
if (auto* dd = dynamic_cast<Dialog*>(d.get()))
|
||||
dd->setInitialization(true); // 强制对话框在新尺寸下重建布局/快照
|
||||
dd->recenterInHostWindow(); // 窗口变化时仅重新居中,不拉伸 Dialog 自身
|
||||
|
||||
// 重绘
|
||||
for (auto& c : controls) c->draw();
|
||||
|
||||
+13
-1
@@ -195,11 +195,22 @@ int main()
|
||||
os.clear();
|
||||
}
|
||||
}
|
||||
selectionAreaLabel->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
|
||||
selectionAreaLabel->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
|
||||
selectionArea->addControl(std::move(selectionAreaLabel));
|
||||
for (auto& s : selectionAreaButton)
|
||||
{
|
||||
s->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
|
||||
s->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
selectionArea->addControl(std::move(s));
|
||||
}
|
||||
for (auto& s : selectionAreaButtonLabel)
|
||||
{
|
||||
s->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
|
||||
s->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
selectionArea->addControl(std::move(s));
|
||||
}
|
||||
//功能区控件
|
||||
//功能区总容器
|
||||
auto function = std::make_unique<Canvas>(10, 170, 680, 70);
|
||||
@@ -536,7 +547,7 @@ int main()
|
||||
signedTogglePtr->setOnToggleOnListener([&]() {
|
||||
gSigned = true;
|
||||
signedTogglePtr->setButtonText("有符号");
|
||||
StellarX::MessageBox::showAsync(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
|
||||
StellarX::MessageBox::showModal(mainWindow, "有符号模式下,\n最高位为符号位,\n其余位为数值位。", "有符号模式");
|
||||
// 立即刷新十进制显示:用当前位图算出新值,仅改 dec
|
||||
auto cur = snapshotBits();
|
||||
const uint32_t u = [&] { uint32_t v = 0; for (int b = 0; b < 32; ++b) if (cur[b]) v |= (1u << b); return v; }();
|
||||
@@ -562,6 +573,7 @@ int main()
|
||||
|
||||
selectionArea->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::Left);
|
||||
selectionArea->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
|
||||
mainWindow.addControl(std::move(selectionArea));
|
||||
mainWindow.addControl(std::move(function));
|
||||
mainWindow.addControl(std::move(NumericalDisplayArea));
|
||||
|
||||
Reference in New Issue
Block a user