Snapshot before repaint phase 2
This commit is contained in:
+22
-7
@@ -1,5 +1,6 @@
|
||||
#include "Button.h"
|
||||
#include "SxLog.h"
|
||||
#include "Window.h"
|
||||
|
||||
Button::Button(int x, int y, int width, int height, const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape)
|
||||
: Control(x, y, width, height)
|
||||
@@ -269,6 +270,9 @@ void Button::draw()
|
||||
|
||||
restoreStyle();//恢复默认字体样式和颜色
|
||||
dirty = false; //标记按钮不需要重绘
|
||||
|
||||
if (tipEnabled && tipVisible)
|
||||
tipLabel.draw();
|
||||
}
|
||||
// 处理鼠标事件,检测点击和悬停状态
|
||||
// 根据按钮模式和形状进行不同的处理
|
||||
@@ -422,20 +426,28 @@ bool Button::handleEvent(const ExMessage& msg)
|
||||
if (stateChanged)
|
||||
dirty = true;
|
||||
const bool tipVisibilityChanged = (tipVisible != oldTipVisible);
|
||||
if (tipVisibilityChanged)
|
||||
dirty = true;
|
||||
|
||||
// 鼠标命中按钮区域,或按钮自身状态因此发生变化时,吞掉该事件。
|
||||
// 这样可以避免被遮挡/重叠的下层控件继续收到同一事件并把自己重绘到上层。
|
||||
if (isMouseMessage && (hover || oldHover || click != oldClick))
|
||||
// 事件吞噬规则:
|
||||
// - 鼠标移动:只有“当前命中按钮”时才吞掉,避免前一个按钮在清 hover 时截断消息,
|
||||
// 导致后一个真正命中的按钮收不到 WM_MOUSEMOVE。
|
||||
// - 鼠标按下/抬起:命中按钮区域时吞掉,避免点击穿透到底层控件。
|
||||
if (msg.message == WM_MOUSEMOVE)
|
||||
{
|
||||
if (hover)
|
||||
consume = true;
|
||||
}
|
||||
else if ((msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONUP) && hover)
|
||||
{
|
||||
consume = true;
|
||||
}
|
||||
markEventVisualChanged(stateChanged || tipVisibilityChanged);
|
||||
|
||||
// 如果需要重绘,立即执行
|
||||
if (dirty)
|
||||
requestRepaint(parent);
|
||||
|
||||
if (tipEnabled && tipVisible)
|
||||
tipLabel.draw();
|
||||
|
||||
return consume;
|
||||
}
|
||||
|
||||
@@ -671,7 +683,10 @@ void Button::hideTooltip()
|
||||
if (tipVisible)
|
||||
{
|
||||
tipVisible = false;
|
||||
tipLabel.hide(); // 还原快照+作废,防止残影
|
||||
if (auto* host = getHostWindow(); host && host->isManagedDispatchActive())
|
||||
tipLabel.invalidateBackgroundSnapshot();
|
||||
else
|
||||
tipLabel.hide(); // 还原快照+作废,防止残影
|
||||
tipHoverTick = GetTickCount64(); // 重置计时基线
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Canvas.h"
|
||||
#include "SxLog.h"
|
||||
#include "Window.h"
|
||||
|
||||
static bool SxIsNoisyMsg(UINT m)
|
||||
{
|
||||
@@ -432,6 +433,13 @@ void Canvas::onWindowResize()
|
||||
|
||||
void Canvas::requestRepaint(Control* parent)
|
||||
{
|
||||
if (shouldDeferManagedRepaint())
|
||||
{
|
||||
if (auto* host = getHostWindow())
|
||||
host->requestManagedRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this == parent)
|
||||
{
|
||||
if (!show)
|
||||
|
||||
+38
@@ -1,6 +1,7 @@
|
||||
#include "Control.h"
|
||||
#include "SxLog.h"
|
||||
#include<assert.h>
|
||||
#include "Window.h"
|
||||
|
||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||
{
|
||||
@@ -123,6 +124,13 @@ void Control::restoreStyle()
|
||||
|
||||
void Control::requestRepaint(Control* parent)
|
||||
{
|
||||
if (shouldDeferManagedRepaint())
|
||||
{
|
||||
if (auto* host = getHostWindow())
|
||||
host->requestManagedRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
// 说明:
|
||||
// - 常规路径:子控件调用 requestRepaint(this->parent),然后 parent 负责局部重绘(Canvas/TabControl override)
|
||||
// - 兜底路径:如果某个“容器控件”没 override requestRepaint,就会出现 parent==this 的递归风险
|
||||
@@ -147,6 +155,13 @@ void Control::requestRepaint(Control* parent)
|
||||
|
||||
void Control::onRequestRepaintAsRoot()
|
||||
{
|
||||
if (shouldDeferManagedRepaint())
|
||||
{
|
||||
if (auto* host = getHostWindow())
|
||||
host->requestManagedRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
SX_LOG_TRACE("Dirty")
|
||||
<< SX_T("触发根重绘:id=", "onRequestRepaintAsRoot: id=") << id
|
||||
<< SX_T("(从根节点开始重画)", " (root repaint)");
|
||||
@@ -157,6 +172,29 @@ void Control::onRequestRepaintAsRoot()
|
||||
draw(); // 只有“无父”时才允许立即画,不会被谁覆盖
|
||||
}
|
||||
|
||||
bool Control::shouldDeferManagedRepaint() const
|
||||
{
|
||||
Window* host = getHostWindow();
|
||||
return host && host->isManagedDispatchActive();
|
||||
}
|
||||
|
||||
Window* Control::getHostWindow() const
|
||||
{
|
||||
if (hostWindow)
|
||||
return hostWindow;
|
||||
return parent ? parent->getHostWindow() : nullptr;
|
||||
}
|
||||
|
||||
RECT Control::getBoundsRect() const
|
||||
{
|
||||
RECT rc{};
|
||||
rc.left = x;
|
||||
rc.top = y;
|
||||
rc.right = x + width;
|
||||
rc.bottom = y + height;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void Control::saveBackground(int x, int y, int w, int h)
|
||||
{
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <functional>
|
||||
#include "CoreTypes.h"
|
||||
|
||||
class Window;
|
||||
|
||||
class Control
|
||||
{
|
||||
protected:
|
||||
@@ -39,6 +41,7 @@ protected:
|
||||
int localx, x, localy, y; // 左上角坐标
|
||||
int localWidth, width, localHeight, height; // 控件尺寸
|
||||
Control* parent = nullptr; // 父控件
|
||||
Window* hostWindow = nullptr; // 宿主窗口(顶层由 Window 注入,子控件可沿 parent 回溯)
|
||||
bool dirty = true; // 是否重绘
|
||||
bool show = true; // 是否显示
|
||||
bool eventVisualChanged = false; // 最近一次 handleEvent 是否真的引发了视觉变化
|
||||
@@ -82,6 +85,7 @@ protected:
|
||||
virtual void requestRepaint(Control* parent);
|
||||
//根控件/无父时触发重绘
|
||||
virtual void onRequestRepaintAsRoot();
|
||||
bool shouldDeferManagedRepaint() const;
|
||||
protected:
|
||||
//保存背景快照
|
||||
virtual void saveBackground(int x, int y, int w, int h);
|
||||
@@ -121,6 +125,10 @@ public:
|
||||
virtual void setIsVisible(bool show);
|
||||
//设置父容器指针
|
||||
void setParent(Control* parent) { this->parent = parent; }
|
||||
//设置宿主窗口(通常仅由顶层 Window/对话框注入)
|
||||
virtual void setHostWindow(Window* host) { this->hostWindow = host; }
|
||||
Window* getHostWindow() const;
|
||||
RECT getBoundsRect() const;
|
||||
//设置是否重绘
|
||||
virtual void setDirty(bool dirty) { this->dirty = dirty; }
|
||||
//检查控件是否可见
|
||||
|
||||
@@ -5,6 +5,7 @@ Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::Messa
|
||||
: Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
||||
{
|
||||
this->id = "Dialog";
|
||||
setHostWindow(&hWnd);
|
||||
show = false;
|
||||
}
|
||||
|
||||
@@ -757,6 +758,13 @@ std::unique_ptr<Button> Dialog::createDialogButton(int x, int y, const std::stri
|
||||
|
||||
void Dialog::requestRepaint(Control* parent)
|
||||
{
|
||||
if (shouldDeferManagedRepaint())
|
||||
{
|
||||
if (auto* host = getHostWindow())
|
||||
host->requestManagedRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this == parent)
|
||||
{
|
||||
for (auto& control : controls)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "TabControl.h"
|
||||
#include "SxLog.h"
|
||||
#include "Window.h"
|
||||
inline void TabControl::initTabBar()
|
||||
{
|
||||
if (controls.empty())return;
|
||||
@@ -415,6 +416,13 @@ void TabControl::setDirty(bool dirty)
|
||||
|
||||
void TabControl::requestRepaint(Control* parent)
|
||||
{
|
||||
if (shouldDeferManagedRepaint())
|
||||
{
|
||||
if (auto* host = getHostWindow())
|
||||
host->requestManagedRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this == parent)
|
||||
{
|
||||
for (auto& control : controls)
|
||||
|
||||
@@ -193,6 +193,9 @@ void Table::initButton()
|
||||
nextButton->setY(btnY);
|
||||
}
|
||||
|
||||
prevButton->setParent(this);
|
||||
nextButton->setParent(this);
|
||||
|
||||
prevButton->textStyle = this->textStyle;
|
||||
nextButton->textStyle = this->textStyle;
|
||||
prevButton->setFillMode(tableFillMode);
|
||||
@@ -262,6 +265,8 @@ void Table::initPageNum()
|
||||
pageNum->setY(pY);
|
||||
}
|
||||
|
||||
pageNum->setParent(this);
|
||||
|
||||
pageNum->textStyle = this->textStyle;
|
||||
if (StellarX::FillMode::Null == tableFillMode)
|
||||
pageNum->setTextdisap(true); // 透明文本
|
||||
|
||||
+97
-122
@@ -27,6 +27,73 @@ static const char* SxMsgName(UINT m)
|
||||
}
|
||||
}
|
||||
|
||||
bool Window::isManagedDispatchActive() const
|
||||
{
|
||||
return managedDispatchActive;
|
||||
}
|
||||
|
||||
void Window::requestManagedRepaint()
|
||||
{
|
||||
managedSceneDirty = true;
|
||||
}
|
||||
|
||||
void Window::drawWindowBackground()
|
||||
{
|
||||
if (!bkImageFile.empty())
|
||||
{
|
||||
if (!background || background->getwidth() != width || background->getheight() != height)
|
||||
{
|
||||
background = std::make_unique<IMAGE>();
|
||||
loadimage(background.get(), bkImageFile.c_str(), width, height, true);
|
||||
}
|
||||
putimage(0, 0, background.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::redrawScene(bool forceControlsDirty, bool forceDialogsDirty)
|
||||
{
|
||||
drawWindowBackground();
|
||||
|
||||
for (auto& c : controls)
|
||||
{
|
||||
if (forceControlsDirty)
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (forceDialogsDirty && d->IsVisible())
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::flushManagedRepaint()
|
||||
{
|
||||
if (!managedSceneDirty || !hWnd)
|
||||
return;
|
||||
|
||||
BeginBatchDraw();
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
managedSceneDirty = false;
|
||||
}
|
||||
|
||||
void Window::dispatchSyntheticMouseMoveToControls(short x, short y)
|
||||
{
|
||||
ExMessage mm{};
|
||||
mm.message = WM_MOUSEMOVE;
|
||||
mm.x = x;
|
||||
mm.y = y;
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
(*it)->handleEvent(mm);
|
||||
}
|
||||
|
||||
/**
|
||||
* ApplyResizableStyle
|
||||
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
|
||||
@@ -294,18 +361,8 @@ void Window::draw()
|
||||
cls &= ~(CS_HREDRAW | CS_VREDRAW);
|
||||
SetClassLongPtr(hWnd, GCL_STYLE, cls);
|
||||
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
d->draw();
|
||||
}
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
@@ -345,19 +402,9 @@ void Window::draw(std::string imagePath)
|
||||
background.reset();
|
||||
}
|
||||
background = std::make_unique<IMAGE>();
|
||||
loadimage(background.get(), bkImageFile.c_str(), width, height, true);
|
||||
putimage(0, 0, background.get());
|
||||
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
d->draw();
|
||||
}
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
@@ -382,7 +429,6 @@ int Window::runEventLoop()
|
||||
{
|
||||
|
||||
bool consume = false; // 事件是否被消费的标志(用于输入事件分发)
|
||||
bool redrawDialogs = false; // 是否需要重绘对话框(控件事件可能引起对话框状态变化)
|
||||
|
||||
if (peekmessage(&msg, EX_MOUSE | EX_KEY | EX_WINDOW, true))
|
||||
{
|
||||
@@ -427,18 +473,8 @@ int Window::runEventLoop()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool hasVisibleNonModalDialog = false;
|
||||
for (const auto& d : dialogs)
|
||||
{
|
||||
if (d->IsVisible() && !d->model())
|
||||
{
|
||||
hasVisibleNonModalDialog = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool controlVisualChanged = false;
|
||||
|
||||
// 输入优先:先给顶层“非模态对话框”,再传给普通控件
|
||||
managedDispatchActive = true;
|
||||
for (auto it = dialogs.rbegin(); it != dialogs.rend(); ++it)
|
||||
{
|
||||
auto& d = *it;
|
||||
@@ -449,6 +485,12 @@ int Window::runEventLoop()
|
||||
if (!SxIsNoisyMsg(msg.message))
|
||||
SX_LOGD("Event") << SX_T("事件被非模态对话框处理:", "Event consumed by non-modal dialog: ")
|
||||
<< SxMsgName(msg.message);
|
||||
|
||||
// 非模态对话框吞掉自己的区域内鼠标移动后,底层普通控件收不到“离开”消息,
|
||||
// 会残留 hover。这里补一条落在窗口外的合成移动,只用于清理底层 hover,
|
||||
// 不会让底层控件重新命中。
|
||||
if (msg.message == WM_MOUSEMOVE)
|
||||
dispatchSyntheticMouseMoveToControls(-32768, -32768);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -458,8 +500,6 @@ int Window::runEventLoop()
|
||||
{
|
||||
Control* current = it->get();
|
||||
consume = current->handleEvent(msg);
|
||||
if (current->didEventAffectVisual())
|
||||
controlVisualChanged = true;
|
||||
if (consume)
|
||||
{
|
||||
if (!SxIsNoisyMsg(msg.message))
|
||||
@@ -469,20 +509,8 @@ int Window::runEventLoop()
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasVisibleNonModalDialog && controlVisualChanged)
|
||||
redrawDialogs = true; // 只有普通控件真的发生视觉变化时,才补画非模态对话框
|
||||
}
|
||||
}
|
||||
// 关键点⑦:事件处理后,如果对话框状态可能变化(例如控件事件引起的控件重绘可能导致对话框被覆盖),优先重绘对话框以确保界面响应及时;后续再根据 needResizeDirty 进行一次性重绘。
|
||||
if (redrawDialogs)
|
||||
{
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (!d->model() && d->IsVisible())
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
redrawDialogs = false; // 重置标志
|
||||
managedDispatchActive = false;
|
||||
}
|
||||
|
||||
//如果有对话框打开或者关闭强制重绘
|
||||
@@ -508,31 +536,21 @@ int Window::runEventLoop()
|
||||
{
|
||||
ScreenToClient(this->hWnd, &pt);
|
||||
ExMessage mm;
|
||||
mm.message = WM_MOUSEMOVE;
|
||||
mm.x = (short)pt.x;
|
||||
mm.y = (short)pt.y;
|
||||
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
(*it)->handleEvent(mm);
|
||||
managedDispatchActive = true;
|
||||
dispatchSyntheticMouseMoveToControls((short)pt.x, (short)pt.y);
|
||||
managedDispatchActive = false;
|
||||
}
|
||||
dialogClose = false; // 重置标志
|
||||
}
|
||||
|
||||
BeginBatchDraw();
|
||||
SX_LOGD("Event") << SX_T("对话框打开/关闭,触发全量重绘", "The dialog box opens/closes, triggering a full redraw");
|
||||
// 先绘制普通控件
|
||||
for (auto& c : controls)
|
||||
c->draw();
|
||||
// 然后绘制对话框(确保对话框在最上层)
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
if (!d->model() && d->IsVisible())
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
needredraw = false;
|
||||
dialogOpen = false;
|
||||
managedSceneDirty = false;
|
||||
|
||||
}
|
||||
// —— 统一收口(needResizeDirty 为真时执行一次性重绘)——
|
||||
@@ -579,25 +597,9 @@ int Window::runEventLoop()
|
||||
int confirmedWidth = clientRect.right - clientRect.left;
|
||||
int confirmedHeight = clientRect.bottom - clientRect.top;
|
||||
|
||||
int renderWidth = confirmedWidth;
|
||||
int renderHeight = confirmedHeight;
|
||||
|
||||
// 背景:若设置了背景图则重载并铺满;否则清屏为纯色
|
||||
if (background && !bkImageFile.empty())
|
||||
{
|
||||
background = std::make_unique<IMAGE>();
|
||||
loadimage(background.get(), bkImageFile.c_str(), renderWidth, renderHeight, true);
|
||||
putimage(0, 0, background.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
}
|
||||
|
||||
// 最终提交“当前已应用尺寸”(用于外部查询/下次比较)
|
||||
width = renderWidth;
|
||||
height = renderHeight;
|
||||
width = confirmedWidth;
|
||||
height = confirmedHeight;
|
||||
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
@@ -609,8 +611,7 @@ int Window::runEventLoop()
|
||||
}
|
||||
|
||||
// 统一批量绘制
|
||||
for (auto& c : controls) c->draw();
|
||||
for (auto& d : dialogs) d->draw();
|
||||
redrawScene(true, true);
|
||||
|
||||
EndBatchDraw();
|
||||
|
||||
@@ -621,8 +622,12 @@ int Window::runEventLoop()
|
||||
SX_LOGI("Resize") << SX_T("尺寸调整已完成:width=","Resize settle done: width=") << width << " height=" << height;
|
||||
|
||||
needResizeDirty = false; // 收口完成,清标志
|
||||
managedSceneDirty = false;
|
||||
}
|
||||
|
||||
if (!needResizeDirty && !dialogOpen && !dialogClose)
|
||||
flushManagedRepaint();
|
||||
|
||||
// 轻微睡眠,削峰填谷(不阻塞拖拽体验)
|
||||
Sleep(10);
|
||||
}
|
||||
@@ -638,20 +643,8 @@ void Window::setBkImage(std::string pImgFile)
|
||||
background = std::make_unique<IMAGE>();
|
||||
bkImageFile = std::move(pImgFile);
|
||||
|
||||
loadimage(background.get(), bkImageFile.c_str(), width, height, true);
|
||||
putimage(0, 0, background.get());
|
||||
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
@@ -659,20 +652,11 @@ void Window::setBkcolor(COLORREF c)
|
||||
{
|
||||
// 更换纯色背景:立即清屏并批量重绘控件/对话框
|
||||
wBkcolor = c;
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
background.reset();
|
||||
bkImageFile.clear();
|
||||
|
||||
BeginBatchDraw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c->setDirty(true);
|
||||
c->draw();
|
||||
}
|
||||
for (auto& d : dialogs)
|
||||
{
|
||||
d->setDirty(true);
|
||||
d->draw();
|
||||
}
|
||||
redrawScene(true, true);
|
||||
EndBatchDraw();
|
||||
}
|
||||
|
||||
@@ -687,12 +671,14 @@ void Window::setHeadline(std::string title)
|
||||
void Window::addControl(std::unique_ptr<Control> control)
|
||||
{
|
||||
// 新增控件:仅加入管理容器,具体绘制在 draw()/收口时统一进行
|
||||
control->setHostWindow(this);
|
||||
controls.push_back(std::move(control));
|
||||
}
|
||||
|
||||
void Window::addDialog(std::unique_ptr<Control> dlg)
|
||||
{
|
||||
// 新增非模态对话框:管理顺序决定事件优先级(顶层从后往前)
|
||||
dlg->setHostWindow(this);
|
||||
dialogs.push_back(std::move(dlg));
|
||||
}
|
||||
|
||||
@@ -783,17 +769,6 @@ void Window::pumpResizeIfNeeded()
|
||||
// Resize + 背景
|
||||
Resize(nullptr, finalW, finalH);
|
||||
GetClientRect(hWnd, &rc);
|
||||
if (background && !bkImageFile.empty())
|
||||
{
|
||||
background = std::make_unique<IMAGE>();
|
||||
loadimage(background.get(), bkImageFile.c_str(), rc.right - rc.left, rc.bottom - rc.top, true);
|
||||
putimage(0, 0, background.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
setbkcolor(wBkcolor);
|
||||
cleardevice();
|
||||
}
|
||||
width = rc.right - rc.left; height = rc.bottom - rc.top;
|
||||
|
||||
// 通知控件/对话框
|
||||
@@ -806,8 +781,7 @@ void Window::pumpResizeIfNeeded()
|
||||
dd->recenterInHostWindow(); // 窗口变化时仅重新居中,不拉伸 Dialog 自身
|
||||
|
||||
// 重绘
|
||||
for (auto& c : controls) c->draw();
|
||||
for (auto& d : dialogs) d->draw();
|
||||
redrawScene(true, true);
|
||||
|
||||
EndBatchDraw();
|
||||
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
|
||||
@@ -818,6 +792,7 @@ void Window::pumpResizeIfNeeded()
|
||||
ValidateRect(hWnd, nullptr);
|
||||
|
||||
needResizeDirty = false;
|
||||
managedSceneDirty = false;
|
||||
}
|
||||
void Window::scheduleResizeFromModal(int w, int h)
|
||||
{
|
||||
|
||||
@@ -54,6 +54,8 @@ class Window
|
||||
// —— 控件/对话框 ——(容器内的普通控件与非模态对话框)
|
||||
std::vector<std::unique_ptr<Control>> controls;
|
||||
std::vector<std::unique_ptr<Control>> dialogs;
|
||||
bool managedDispatchActive = false; // 事件分发期:控件只改状态,不立即画
|
||||
bool managedSceneDirty = false; // 本轮分发是否登记了统一重绘请求
|
||||
|
||||
public:
|
||||
bool dialogClose = false; // 项目内使用的状态位,对话框关闭标志
|
||||
@@ -95,6 +97,12 @@ public:
|
||||
// —— 尺寸调整 ——(供内部与外部调用的尺寸变化处理)
|
||||
void pumpResizeIfNeeded(); // 执行一次统一收口重绘
|
||||
void scheduleResizeFromModal(int w, int h);
|
||||
bool isManagedDispatchActive() const;
|
||||
void requestManagedRepaint();
|
||||
void flushManagedRepaint();
|
||||
private:
|
||||
void adaptiveLayout(std::unique_ptr<Control>& c, const int finalH, const int finalW);
|
||||
void redrawScene(bool forceControlsDirty, bool forceDialogsDirty);
|
||||
void drawWindowBackground();
|
||||
void dispatchSyntheticMouseMoveToControls(short x, short y);
|
||||
};
|
||||
|
||||
+193
-2
@@ -1,6 +1,6 @@
|
||||
// 本工具基于 StellarX 构建,轻量级的 Windows GUI 框架。
|
||||
// StellarX 星垣GUI框架 - 测试用例
|
||||
#include"StellarX.h"
|
||||
#define KEY 2
|
||||
#define KEY 4
|
||||
|
||||
#if 1 == KEY
|
||||
int main()
|
||||
@@ -106,6 +106,197 @@ int main()
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 4 == KEY
|
||||
|
||||
#include"StellarX.h"
|
||||
#include <vector>
|
||||
|
||||
int main()
|
||||
{
|
||||
StellarX::SxLogger::setGBK();
|
||||
StellarX::SxLogger::Get().enableConsole(true);
|
||||
StellarX::SxLogger::Get().setMinLevel(StellarX::SxLogLevel::Debug);
|
||||
StellarX::SxLogger::Get().setLanguage(StellarX::SxLogLanguage::ZhCN);
|
||||
|
||||
Window win(1280, 780, 1, RGB(242, 246, 250), "StellarX 综合回归测试用例 4");
|
||||
|
||||
auto infoPanel = std::make_unique<Canvas>(20, 20, 1240, 90);
|
||||
auto infoPanelPtr = infoPanel.get();
|
||||
infoPanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
infoPanelPtr->setCanvasBkColor(RGB(225, 234, 244));
|
||||
infoPanelPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
infoPanelPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||
|
||||
auto infoTitle = std::make_unique<Label>(20, 10, "综合回归测试 4:对话框遮挡、快速 Hover、分页按钮、模态 Resize");
|
||||
infoTitle->textStyle.nHeight = 26;
|
||||
infoTitle->setTextdisap(true);
|
||||
auto infoLine1 = std::make_unique<Label>(20, 42, "1. 点击右侧按钮打开非模态/模态对话框;2. 在顶部按钮、页签、分页按钮附近快速划动光标。");
|
||||
infoLine1->setTextdisap(true);
|
||||
auto infoLine2 = std::make_unique<Label>(20, 62, "3. 模态对话框打开后拖动窗口大小;4. 关闭对话框后观察底层按钮 Hover 是否立即恢复。");
|
||||
infoLine2->setTextdisap(true);
|
||||
|
||||
auto openAsyncButton = std::make_unique<Button>(930, 22, 130, 42, "打开非模态");
|
||||
auto openAsyncButtonPtr = openAsyncButton.get();
|
||||
openAsyncButtonPtr->enableTooltip(true);
|
||||
openAsyncButtonPtr->setTooltipText("打开长文本非模态对话框,覆盖顶部按钮 / 页签 / 分页区");
|
||||
|
||||
auto openModalButton = std::make_unique<Button>(1080, 22, 130, 42, "打开模态");
|
||||
auto openModalButtonPtr = openModalButton.get();
|
||||
openModalButtonPtr->enableTooltip(true);
|
||||
openModalButtonPtr->setTooltipText("打开模态对话框并测试拖拽窗口后的重绘与关闭");
|
||||
|
||||
infoPanelPtr->addControl(std::move(infoTitle));
|
||||
infoPanelPtr->addControl(std::move(infoLine1));
|
||||
infoPanelPtr->addControl(std::move(infoLine2));
|
||||
infoPanelPtr->addControl(std::move(openAsyncButton));
|
||||
infoPanelPtr->addControl(std::move(openModalButton));
|
||||
|
||||
auto hoverCanvas = std::make_unique<Canvas>(20, 140, 1240, 100);
|
||||
auto hoverCanvasPtr = hoverCanvas.get();
|
||||
hoverCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
hoverCanvasPtr->setCanvasBkColor(RGB(230, 238, 248));
|
||||
hoverCanvasPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
hoverCanvasPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||
|
||||
auto hoverTitle = std::make_unique<Label>(20, 8, "区域 A:打开非模态对话框后,请在下面 8 个按钮之间快速来回划动鼠标。");
|
||||
hoverTitle->setTextdisap(true);
|
||||
hoverCanvasPtr->addControl(std::move(hoverTitle));
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
auto hoverButton = std::make_unique<Button>(250 + i * 92, 38, 72, 38, "B" + std::to_string(i + 1));
|
||||
hoverButton->enableTooltip(true);
|
||||
hoverButton->setTooltipText("顶部 Hover 按钮 " + std::to_string(i + 1));
|
||||
hoverButton->setButtonShape(StellarX::ControlShape::B_RECTANGLE);
|
||||
hoverCanvasPtr->addControl(std::move(hoverButton));
|
||||
}
|
||||
|
||||
auto tabControl = std::make_unique<TabControl>(120, 260, 1040, 180);
|
||||
auto tabControlPtr = tabControl.get();
|
||||
tabControlPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
tabControlPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
||||
tabControlPtr->setTabPlacement(StellarX::TabPlacement::Top);
|
||||
tabControlPtr->setTabBarHeight(28);
|
||||
tabControlPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
tabControlPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||
|
||||
auto page1 = std::make_unique<Canvas>(0, 0, 1040, 152);
|
||||
page1->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
page1->setCanvasBkColor(RGB(246, 250, 255));
|
||||
auto page2 = std::make_unique<Canvas>(0, 0, 1040, 152);
|
||||
page2->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
page2->setCanvasBkColor(RGB(250, 248, 242));
|
||||
auto page3 = std::make_unique<Canvas>(0, 0, 1040, 152);
|
||||
page3->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||
page3->setCanvasBkColor(RGB(243, 250, 244));
|
||||
|
||||
auto tabPage1Ptr = page1.get();
|
||||
auto tabPage2Ptr = page2.get();
|
||||
auto tabPage3Ptr = page3.get();
|
||||
|
||||
tabControlPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 120, 28, "页签-事件区"), std::move(page1)));
|
||||
tabControlPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 120, 28, "页签-空白区"), std::move(page2)));
|
||||
tabControlPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 120, 28, "页签-辅助区"), std::move(page3)));
|
||||
tabControlPtr->setActiveIndex(0);
|
||||
|
||||
auto page1Label = std::make_unique<Label>(20, 10, "区域 B:打开非模态对话框后,切换页签并在页内按钮上快速 Hover。");
|
||||
page1Label->setTextdisap(true);
|
||||
tabPage1Ptr->addControl(std::move(page1Label));
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
auto pageButton = std::make_unique<Button>(190 + i * 150, 55, 110, 38, "页内按钮" + std::to_string(i + 1));
|
||||
pageButton->enableTooltip(true);
|
||||
pageButton->setTooltipText("页签区按钮 " + std::to_string(i + 1));
|
||||
tabPage1Ptr->addControl(std::move(pageButton));
|
||||
}
|
||||
|
||||
auto page2Label = std::make_unique<Label>(30, 55, "区域 B-2:此页保留较大空白区,用来观察 Hover 离开空白区域时的恢复。");
|
||||
page2Label->setTextdisap(true);
|
||||
tabPage2Ptr->addControl(std::move(page2Label));
|
||||
|
||||
auto page3Label = std::make_unique<Label>(20, 18, "区域 B-3:辅助页,用来测试页签切换本身的重绘是否稳定。");
|
||||
page3Label->setTextdisap(true);
|
||||
auto page3Button = std::make_unique<Button>(420, 65, 180, 40, "辅助按钮");
|
||||
page3Button->enableTooltip(true);
|
||||
page3Button->setTooltipText("辅助页按钮 Tooltip");
|
||||
tabPage3Ptr->addControl(std::move(page3Label));
|
||||
tabPage3Ptr->addControl(std::move(page3Button));
|
||||
|
||||
auto table = std::make_unique<Table>(240, 470);
|
||||
auto tablePtr = table.get();
|
||||
tablePtr->setHeaders({ "编号", "名称", "状态", "备注" });
|
||||
tablePtr->setData({
|
||||
{"01", "顶部按钮覆盖测试", "待验证", "快速 Hover 后不能残留高亮"},
|
||||
{"02", "页签切换测试", "待验证", "页签按钮与页内容不应穿透"},
|
||||
{"03", "分页按钮测试", "待验证", "被非模态部分覆盖时仍应稳定"},
|
||||
{"04", "模态 Resize 测试", "待验证", "拖窗后标题、关闭按钮不应残影"},
|
||||
{"05", "关闭后 Hover 恢复", "待验证", "模态关闭后底层按钮 Hover 应恢复"}
|
||||
});
|
||||
tablePtr->setRowsPerPage(2);
|
||||
tablePtr->setTableBorderWidth(1);
|
||||
tablePtr->setTableBorder(RGB(60, 90, 120));
|
||||
tablePtr->setTableFillMode(StellarX::FillMode::Null);
|
||||
tablePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||
tablePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||
|
||||
const std::string asyncText =
|
||||
"KEY4 非模态回归测试正在进行。\n"
|
||||
"请依次检查以下场景:\n"
|
||||
"1. 在顶部按钮之间快速来回移动鼠标。\n"
|
||||
"2. 切换页签,并在页签按钮与页内按钮上快速 Hover。\n"
|
||||
"3. 将鼠标移动到表格分页按钮附近,观察是否闪烁或穿透。\n"
|
||||
"4. 将鼠标移出窗口后再移回窗口。\n"
|
||||
"5. 保持本对话框打开,确认它始终位于最上层。\n"
|
||||
"6. 若需要重复测试,可直接再次点击右上角按钮。\n"
|
||||
"7. 本对话框故意较高,以覆盖顶部按钮、页签区与部分分页区。\n"
|
||||
"8. 顶部按钮区域用于检测快速 Hover 残留。\n"
|
||||
"9. 页签区域用于检测被遮挡时的切换与 Hover。\n"
|
||||
"10. 表格分页区用于检测分页按钮被部分覆盖时的重绘。\n"
|
||||
"11. 若关闭本对话框,请观察底层 Hover 是否立即恢复。\n"
|
||||
"12. 本消息框保留较多行,是为了增大对遮挡场景的覆盖范围。";
|
||||
|
||||
const std::string modalText =
|
||||
"KEY4 模态回归测试。\n"
|
||||
"请在本对话框保持打开时执行:\n"
|
||||
"1. 拖拽窗口大小,观察标题、关闭按钮与功能按钮是否残影。\n"
|
||||
"2. 拖拽后点击关闭按钮。\n"
|
||||
"3. 拖拽后点击功能按钮。\n"
|
||||
"4. 对话框关闭后,将鼠标停在底层顶部按钮上,观察 Hover 是否恢复。";
|
||||
|
||||
openAsyncButtonPtr->setOnClickListener([&win, asyncText]()
|
||||
{
|
||||
StellarX::MessageBox::showAsync(
|
||||
win,
|
||||
asyncText,
|
||||
"KEY4 非模态综合回归",
|
||||
StellarX::MessageBoxType::YesNoCancel,
|
||||
[](StellarX::MessageBoxResult result)
|
||||
{
|
||||
SX_LOGI("KEY4") << "非模态对话框结果=" << (int)result;
|
||||
});
|
||||
});
|
||||
|
||||
openModalButtonPtr->setOnClickListener([&win, modalText]()
|
||||
{
|
||||
auto result = StellarX::MessageBox::showModal(
|
||||
win,
|
||||
modalText,
|
||||
"KEY4 模态综合回归",
|
||||
StellarX::MessageBoxType::OKCancel);
|
||||
SX_LOGI("KEY4") << "模态对话框结果=" << (int)result;
|
||||
});
|
||||
|
||||
win.addControl(std::move(infoPanel));
|
||||
win.addControl(std::move(hoverCanvas));
|
||||
win.addControl(std::move(tabControl));
|
||||
win.addControl(std::move(table));
|
||||
|
||||
win.draw();
|
||||
return win.runEventLoop();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if 2 == KEY
|
||||
// 本工具基于 StellarX 构建,轻量级的 Windows GUI 框架。
|
||||
#include"StellarX.h"
|
||||
|
||||
Reference in New Issue
Block a user