From 77a8fe568a7ed627c0334c9210a7dc44ef8a8e1d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 7 Apr 2026 21:37:49 +0800 Subject: [PATCH] Snapshot before repaint phase 2 --- Button.cpp | 29 +++++-- Canvas.cpp | 8 ++ Control.cpp | 38 +++++++++ Control.h | 8 ++ Dialog.cpp | 8 ++ TabControl.cpp | 8 ++ Table.cpp | 5 ++ Window.cpp | 219 ++++++++++++++++++++++--------------------------- Window.h | 8 ++ z-testDome.cpp | 195 ++++++++++++++++++++++++++++++++++++++++++- 10 files changed, 395 insertions(+), 131 deletions(-) diff --git a/Button.cpp b/Button.cpp index 0781231..a4875db 100644 --- a/Button.cpp +++ b/Button.cpp @@ -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(); // 重置计时基线 } } diff --git a/Canvas.cpp b/Canvas.cpp index dd2f1fa..352129d 100644 --- a/Canvas.cpp +++ b/Canvas.cpp @@ -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) diff --git a/Control.cpp b/Control.cpp index 0d28093..dd71f4e 100644 --- a/Control.cpp +++ b/Control.cpp @@ -1,6 +1,7 @@ #include "Control.h" #include "SxLog.h" #include +#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) { diff --git a/Control.h b/Control.h index b378dc8..3083380 100644 --- a/Control.h +++ b/Control.h @@ -32,6 +32,8 @@ #include #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; } //检查控件是否可见 diff --git a/Dialog.cpp b/Dialog.cpp index 792befa..e8446d6 100644 --- a/Dialog.cpp +++ b/Dialog.cpp @@ -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