From 6a5203733a51962294b9cb23a5552868ff5628e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:27:51 +0000 Subject: [PATCH 01/11] Initial plan From 990ee6287a32ff6c1d6e8c029935bfb952ccc2a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:30:29 +0000 Subject: [PATCH 02/11] docs: add widget performance plan Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- docs/Widget_Performance_Plan.md | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/Widget_Performance_Plan.md diff --git a/docs/Widget_Performance_Plan.md b/docs/Widget_Performance_Plan.md new file mode 100644 index 0000000..b87f2e8 --- /dev/null +++ b/docs/Widget_Performance_Plan.md @@ -0,0 +1,46 @@ +# Widget 渲染性能改进方案(仅方案,未实施) + +> 目标:梳理当前 Widget 体系的绘制链路,找出影响性能的热点,并给出可操作的优化方向。本文仅提供方案,未改动任何代码。 + +## 现状梳理 +- `Panel::draw` 每帧都会清空图层、重绘圆角背景,并在循环中为全部子控件调用 `draw`,即便状态未变化仍整板重绘(`src/Widget.cpp`)。 +- `Panel::draw` 每次绘制都会调用 `layout->apply`,布局无变更也重复计算。 +- `Button`、`InputBox`、`Slider` 等控件有局部缓存(`needRedraw` 等),但在 `Panel` 上层的全量重绘使缓存收益打折。 +- `InputBox::draw` 在聚焦时每帧多次 `measuretext` 计算光标/IME 宽度,即便内容未变化也有重复测量。 +- 各控件的绘制区域总是整块 `putimage`,未利用脏矩形或可视区域裁剪。 + +## 优化方向 +1. **脏矩形 / 重绘标记** + - 为 `Widget`/`Panel` 增加脏标记,只有自身或子控件状态变动时才重绘缓存图层;聚合子控件的脏区域,在最终 `putimage` 时只复制变动区域。 + - 动画控件(Ripple/进度条等)在动画结束后清理脏标记,避免持续刷新。 + +2. **分层缓存与静态背景复用** + - 将静态背景(圆角面板底色、按钮底板)与动态前景(文本、动画)分层缓存:静态层仅在尺寸/颜色/圆角/缩放变化时重绘,动态层按需叠加。 + - 对 `Panel` 支持“冻结”模式:内容静态时直接复用上次合成结果,不再遍历子控件。 + +3. **布局与尺寸缓存** + - 为 `Layout` 维护 `layoutDirty` 标记,在子控件增删、尺寸/缩放变化时才重算 offsets;`Panel::draw` 不再每帧调用 `apply`。 + - 当 `Panel` 缩放时批量更新子控件 offset 后缓存结果,避免每帧重复 `setPosition`/`setScale`。 + +4. **文本测量与字体缓存** + - `InputBox` / `Text`:将 `measuretext` 结果按 `(content, cursorPos, scale, font)` 组合缓存,仅在对应字段变化时重新测量;IME 组合字符串也走相同缓存。 + - 预创建字体对象(LOGFONT)并按 `scale` 分级缓存,减少 `ege_setfont` 调用。 + +5. **可见性与裁剪** + - 为控件增加可见性/裁剪检查,超出父面板或窗口区域时跳过绘制或仅绘制交集区域,降低 `putimage` 面积。 + - 对隐藏或折叠(如 Sidebar 收起)状态,直接跳过子树绘制与事件命中。 + +6. **动画驱动与刷新节流** + - 将 Ripple / 过渡动画驱动放入统一的 ticker,只有动画存活时触发脏标记;无动画时不进入重绘分支。 + - 鼠标悬停/光标闪烁使用计时器节流,降低高频 `draw` 调用。 + +7. **资源与内存管理** + - 为 `newimage` 创建的缓存层加入池化或生命周期复用,避免频繁分配释放(特别是 `setScale` 后的重建)。 + - 对大尺寸面板支持惰性分块缓存(tiles),局部脏区域只更新对应 tile。 + +## 验证与落地建议 +- 增加简单的绘制耗时采样(宏封装 `std::chrono` 或平台计时)输出到调试日志,先定位重绘热点,再按优先级落地。 +- 先从「脏标记 + 静态背景缓存」与「布局缓存」入手,对现有接口侵入小、收益高;其余方案可分阶段逐步实现。 +- 优先在典型场景(含多 Panel/大量文本输入/频繁动画)做 A/B 对比,验证 FPS 与 CPU 占用变化。 + +以上方案可单独或组合实施,可按优先级逐步引入,确保每次改动可测、可回退。 From 66e66ed5edf9cec3a745e060399dce67518280ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 09:05:53 +0000 Subject: [PATCH 03/11] docs: note panel lazy cache using putimage_withalpha Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- docs/Widget_Performance_Plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Widget_Performance_Plan.md b/docs/Widget_Performance_Plan.md index b87f2e8..e306e7b 100644 --- a/docs/Widget_Performance_Plan.md +++ b/docs/Widget_Performance_Plan.md @@ -17,6 +17,7 @@ 2. **分层缓存与静态背景复用** - 将静态背景(圆角面板底色、按钮底板)与动态前景(文本、动画)分层缓存:静态层仅在尺寸/颜色/圆角/缩放变化时重绘,动态层按需叠加。 - 对 `Panel` 支持“冻结”模式:内容静态时直接复用上次合成结果,不再遍历子控件。 + - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`),仅在尺寸/圆角/缩放变化需要重新生成遮罩时再走 `alphafilter`。 3. **布局与尺寸缓存** - 为 `Layout` 维护 `layoutDirty` 标记,在子控件增删、尺寸/缩放变化时才重算 offsets;`Panel::draw` 不再每帧调用 `apply`。 From 51d6ff68be1c475e134d779690b3b18c6eeeab97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 09:06:33 +0000 Subject: [PATCH 04/11] docs: clarify panel lazy cache wording Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- docs/Widget_Performance_Plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Widget_Performance_Plan.md b/docs/Widget_Performance_Plan.md index e306e7b..75111a4 100644 --- a/docs/Widget_Performance_Plan.md +++ b/docs/Widget_Performance_Plan.md @@ -17,7 +17,7 @@ 2. **分层缓存与静态背景复用** - 将静态背景(圆角面板底色、按钮底板)与动态前景(文本、动画)分层缓存:静态层仅在尺寸/颜色/圆角/缩放变化时重绘,动态层按需叠加。 - 对 `Panel` 支持“冻结”模式:内容静态时直接复用上次合成结果,不再遍历子控件。 - - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`),仅在尺寸/圆角/缩放变化需要重新生成遮罩时再走 `alphafilter`。 + - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`),仅在尺寸/圆角/缩放变化需要重新生成遮罩时再走 `putimage_alphafilter`。 3. **布局与尺寸缓存** - 为 `Layout` 维护 `layoutDirty` 标记,在子控件增删、尺寸/缩放变化时才重算 offsets;`Panel::draw` 不再每帧调用 `apply`。 From 2cdf276abfca1144816ac80329546e37426f2037 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 09:07:00 +0000 Subject: [PATCH 05/11] docs: split panel caching note for clarity Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- docs/Widget_Performance_Plan.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Widget_Performance_Plan.md b/docs/Widget_Performance_Plan.md index 75111a4..f367ae5 100644 --- a/docs/Widget_Performance_Plan.md +++ b/docs/Widget_Performance_Plan.md @@ -17,7 +17,8 @@ 2. **分层缓存与静态背景复用** - 将静态背景(圆角面板底色、按钮底板)与动态前景(文本、动画)分层缓存:静态层仅在尺寸/颜色/圆角/缩放变化时重绘,动态层按需叠加。 - 对 `Panel` 支持“冻结”模式:内容静态时直接复用上次合成结果,不再遍历子控件。 - - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`),仅在尺寸/圆角/缩放变化需要重新生成遮罩时再走 `putimage_alphafilter`。 + - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`)。 + - 仅在尺寸 / 圆角 / 缩放变化导致遮罩失效时,再重新生成遮罩并走 `putimage_alphafilter`。 3. **布局与尺寸缓存** - 为 `Layout` 维护 `layoutDirty` 标记,在子控件增删、尺寸/缩放变化时才重算 offsets;`Panel::draw` 不再每帧调用 `apply`。 From 5c39285c163412bfd4b1d3d2768e2c660fc7d470 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 09:07:32 +0000 Subject: [PATCH 06/11] docs: justify panel putimage_withalpha choice Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- docs/Widget_Performance_Plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Widget_Performance_Plan.md b/docs/Widget_Performance_Plan.md index f367ae5..e5d4b1f 100644 --- a/docs/Widget_Performance_Plan.md +++ b/docs/Widget_Performance_Plan.md @@ -17,7 +17,7 @@ 2. **分层缓存与静态背景复用** - 将静态背景(圆角面板底色、按钮底板)与动态前景(文本、动画)分层缓存:静态层仅在尺寸/颜色/圆角/缩放变化时重绘,动态层按需叠加。 - 对 `Panel` 支持“冻结”模式:内容静态时直接复用上次合成结果,不再遍历子控件。 - - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(开销低于 `putimage_alphafilter`)。 + - 依据现有 `Button` 的懒标记思路,为 `Panel` 增加懒标记并复用已遮罩好的缓存层,复用时优先使用 `putimage_withalpha`(直接混合已有 alpha,不再逐像素做遮罩过滤,开销低于 `putimage_alphafilter`)。 - 仅在尺寸 / 圆角 / 缩放变化导致遮罩失效时,再重新生成遮罩并走 `putimage_alphafilter`。 3. **布局与尺寸缓存** From 5a0e192aff1387e5ca8a8ab35ec2bc1c6daa437a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:47:22 +0000 Subject: [PATCH 07/11] feat: add panel lazy redraw cache Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- include/Widget.h | 10 ++++- src/Widget.cpp | 95 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/include/Widget.h b/include/Widget.h index 0ecc514..fe680df 100644 --- a/include/Widget.h +++ b/include/Widget.h @@ -109,6 +109,8 @@ class Panel : public Widget { * @param offsetY 相对于面板中心的 y 偏移 */ void addChild(Widget* child, double offsetX, double offsetY); + void markDirty() { needRedraw = true; activeRedrawFrames = kDefaultActiveFrames; } + void markDirty() { needRedraw = true; activeRedrawFrames = kDefaultActiveFrames; } /** * @brief 绘制 Panel 到指定图层 @@ -189,7 +191,7 @@ class Panel : public Widget { * @brief 设置布局管理器 * @param l 布局对象 */ - void setLayout(std::shared_ptr l) { layout = std::move(l); } + void setLayout(std::shared_ptr l) { layout = std::move(l); markDirty(); } /** * @brief 获取布局管理器 @@ -204,7 +206,11 @@ class Panel : public Widget { double alpha = 255; PIMAGE layer = nullptr; PIMAGE maskLayer = nullptr; + PIMAGE cachedLayer = nullptr; bool scaleChanged = true; + bool needRedraw = true; + int activeRedrawFrames = 0; + static constexpr int kDefaultActiveFrames = 80; std::vector children; std::vector childOffsets; ///< 每个子控件的相对偏移(以面板中心为参考) @@ -1643,4 +1649,4 @@ extern std::map IdToWidget; ///< ID到控件的映射(Wi */ Widget* getWidgetById(const std::wstring& identifier); -void assignOrder(std::vector widgetWithOrder); \ No newline at end of file +void assignOrder(std::vector widgetWithOrder); diff --git a/src/Widget.cpp b/src/Widget.cpp index 95a5758..4e7ca16 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -42,9 +42,11 @@ Panel::Panel(double cx, double cy, double w, double h, double r, color_t bg) { origin_height = height = h; origin_radius = radius = r; layer = newimage(w,h); + cachedLayer = newimage(w,h); maskLayer = newimage(w,h); ege_enable_aa(true,layer); ege_enable_aa(true,maskLayer); + ege_enable_aa(true,cachedLayer); // 遮罩使用不透明颜色:黑色背景(隐藏)和白色填充(显示) // 这在PRGB32模式下更可靠,因为它依赖RGB值而非alpha值进行遮罩 @@ -58,6 +60,8 @@ void Panel::addChild(Widget* child, double offsetX, double offsetY) { children.push_back(child); childOffsets.push_back(Position{ offsetX, offsetY }); child->is_global = false; + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; } void Panel::draw() { @@ -65,41 +69,52 @@ void Panel::draw() { } void Panel::draw(PIMAGE dst, double x, double y) { - if (layout) layout->apply(*this); // 自动计算子控件位置 + bool shouldRedraw = needRedraw || scaleChanged || PanelScaleChanged || activeRedrawFrames > 0; + if (layout && shouldRedraw) layout->apply(*this); // 自动计算子控件位置 double left = x - width / 2; double top = y - height / 2; - // 总是清空并重绘(子控件可能有动态内容) - // 注意:子控件(如Button, InputBox)内部有自己的缓存机制来避免不必要的工作 - // 使用真正的透明色(PRGB32模式下alpha=0时RGB也应为0) - setbkcolor_f(EGEARGB(0, 0, 0, 0), layer); - cleardevice(layer); - - // 绘制自身背景(圆角矩形) - setfillcolor(bgColor, layer); - ege_fillroundrect(0, 0, width, height, radius, radius, radius, radius, layer); - - // 绘制子控件 - if(scaleChanged) PanelScaleChanged = true; - for (size_t i = 0; i < children.size(); ++i) { - double childX = width / 2 + childOffsets[i].x * scale; - double childY = height / 2 + childOffsets[i].y * scale; - absolutPosDeltaX = left; - absolutPosDeltaY = top; - children[i]->setPosition(cx + childOffsets[i].x * scale,cy + childOffsets[i].y * scale); - children[i]->draw(layer, childX, childY); - absolutPosDeltaX = 0; - absolutPosDeltaY = 0; + if (shouldRedraw) { + // 总是清空并重绘(子控件可能有动态内容) + // 注意:子控件(如Button, InputBox)内部有自己的缓存机制来避免不必要的工作 + // 使用真正的透明色(PRGB32模式下alpha=0时RGB也应为0) + setbkcolor_f(EGEARGB(0, 0, 0, 0), layer); + cleardevice(layer); + + // 绘制自身背景(圆角矩形) + setfillcolor(bgColor, layer); + ege_fillroundrect(0, 0, width, height, radius, radius, radius, radius, layer); + + // 绘制子控件 + if(scaleChanged) PanelScaleChanged = true; + for (size_t i = 0; i < children.size(); ++i) { + double childX = width / 2 + childOffsets[i].x * scale; + double childY = height / 2 + childOffsets[i].y * scale; + absolutPosDeltaX = left; + absolutPosDeltaY = top; + children[i]->setPosition(cx + childOffsets[i].x * scale,cy + childOffsets[i].y * scale); + children[i]->draw(layer, childX, childY); + absolutPosDeltaX = 0; + absolutPosDeltaY = 0; + } + PanelScaleChanged = false; + scaleChanged = false; + needRedraw = false; + if (activeRedrawFrames > 0) activeRedrawFrames--; + + // 生成一次遮罩后的缓存层,后续直接用 putimage_withalpha + setbkcolor_f(EGEARGB(0, 0, 0, 0), cachedLayer); + cleardevice(cachedLayer); + putimage_alphafilter(cachedLayer, layer, 0, 0, maskLayer, 0, 0, -1, -1); } - PanelScaleChanged = false; - scaleChanged = false; - // 粘贴到主窗口 - putimage_alphafilter(dst, layer, left, top, maskLayer, 0, 0, -1, -1); + // 粘贴到主窗口,优先使用 withalpha(相对 alphafilter 开销更低) + putimage_withalpha(dst, cachedLayer, left, top); } Panel::~Panel(){ if (layer) delimage(layer); + if (cachedLayer) delimage(cachedLayer); if (maskLayer) delimage(maskLayer); } @@ -115,6 +130,8 @@ Position Panel::getPosition(){ void Panel::setScale(double s){ if(sgn(s - scale) == 0) return; scaleChanged = true; + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; width = origin_width * s; height = origin_height * s; radius = origin_radius * s; @@ -128,8 +145,11 @@ void Panel::setScale(double s){ maskLayer = newimage(width,height); if(layer) delimage(layer); layer = newimage(width,height); + if(cachedLayer) delimage(cachedLayer); + cachedLayer = newimage(width,height); ege_enable_aa(true,layer); ege_enable_aa(true,maskLayer); + ege_enable_aa(true,cachedLayer); // 遮罩使用不透明颜色:黑色背景(隐藏)和白色填充(显示) setbkcolor_f(EGEARGB(255, 0, 0, 0), maskLayer); @@ -179,16 +199,24 @@ bool Panel::handleEvent(const mouse_msg& msg){ } for(Widget* w : children){ bool state = w->handleEvent(msg); - if(state) return true; + if(state) { + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; + return true; + } } if(msg.is_left() && msg.is_down()){ mouseOwningFlag = this; + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; return true; } else if(msg.is_left() && msg.is_up()){ if(mouseOwningFlag == this){ mouseOwningFlag = nullptr; } + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; return false; } return false; @@ -197,12 +225,17 @@ bool Panel::handleEvent(const mouse_msg& msg){ void Panel::setSize(double w,double h){ origin_width = width = w; origin_height = height = h; + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; if(layer) delimage(layer); if(maskLayer) delimage(maskLayer); + if(cachedLayer) delimage(cachedLayer); layer = newimage(width,height); maskLayer = newimage(width,height); + cachedLayer = newimage(width,height); ege_enable_aa(true,layer); ege_enable_aa(true,maskLayer); + ege_enable_aa(true,cachedLayer); // 遮罩使用不透明颜色:黑色背景(隐藏)和白色填充(显示) setbkcolor_f(EGEARGB(255, 0, 0, 0), maskLayer); @@ -214,10 +247,14 @@ void Panel::setSize(double w,double h){ void Panel::clearChildren(){ children.clear(); childOffsets.clear(); + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; } void Panel::setAlpha(double a) { alpha = clamp(a, 0, 255); + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; // 遮罩使用不透明颜色:黑色背景(隐藏)和白色填充(显示) setbkcolor_f(EGEARGB(255, 0, 0, 0), maskLayer); cleardevice(maskLayer); @@ -232,6 +269,8 @@ std::vector& Panel::getChildren() { void Panel::setChildrenOffset(int index,Position pos){ if (index >= 0 && index < static_cast(childOffsets.size())) { childOffsets[index] = pos; + needRedraw = true; + activeRedrawFrames = kDefaultActiveFrames; } } @@ -2978,4 +3017,4 @@ Widget* getWidgetById(const std::wstring& identifier){ void assignOrder(std::vector widgetWithOrder){ swap(widgetWithOrder,widgets); -} \ No newline at end of file +} From 6070af9d091e8ee63793e584bd2d00dba4e566bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:48:32 +0000 Subject: [PATCH 08/11] chore: dedupe panel markDirty helper Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- include/Widget.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/Widget.h b/include/Widget.h index fe680df..e4901e9 100644 --- a/include/Widget.h +++ b/include/Widget.h @@ -110,7 +110,6 @@ class Panel : public Widget { */ void addChild(Widget* child, double offsetX, double offsetY); void markDirty() { needRedraw = true; activeRedrawFrames = kDefaultActiveFrames; } - void markDirty() { needRedraw = true; activeRedrawFrames = kDefaultActiveFrames; } /** * @brief 绘制 Panel 到指定图层 From c7d26218a25feb7ac4faf09b37c9312270717626 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:49:45 +0000 Subject: [PATCH 09/11] refactor: reuse panel markDirty helper Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- src/Widget.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Widget.cpp b/src/Widget.cpp index 4e7ca16..2d89004 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -60,8 +60,7 @@ void Panel::addChild(Widget* child, double offsetX, double offsetY) { children.push_back(child); childOffsets.push_back(Position{ offsetX, offsetY }); child->is_global = false; - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); } void Panel::draw() { @@ -130,8 +129,7 @@ Position Panel::getPosition(){ void Panel::setScale(double s){ if(sgn(s - scale) == 0) return; scaleChanged = true; - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); width = origin_width * s; height = origin_height * s; radius = origin_radius * s; @@ -200,23 +198,20 @@ bool Panel::handleEvent(const mouse_msg& msg){ for(Widget* w : children){ bool state = w->handleEvent(msg); if(state) { - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); return true; } } if(msg.is_left() && msg.is_down()){ mouseOwningFlag = this; - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); return true; } else if(msg.is_left() && msg.is_up()){ if(mouseOwningFlag == this){ mouseOwningFlag = nullptr; } - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); return false; } return false; @@ -225,8 +220,7 @@ bool Panel::handleEvent(const mouse_msg& msg){ void Panel::setSize(double w,double h){ origin_width = width = w; origin_height = height = h; - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); if(layer) delimage(layer); if(maskLayer) delimage(maskLayer); if(cachedLayer) delimage(cachedLayer); @@ -247,14 +241,12 @@ void Panel::setSize(double w,double h){ void Panel::clearChildren(){ children.clear(); childOffsets.clear(); - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); } void Panel::setAlpha(double a) { alpha = clamp(a, 0, 255); - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); // 遮罩使用不透明颜色:黑色背景(隐藏)和白色填充(显示) setbkcolor_f(EGEARGB(255, 0, 0, 0), maskLayer); cleardevice(maskLayer); @@ -269,8 +261,7 @@ std::vector& Panel::getChildren() { void Panel::setChildrenOffset(int index,Position pos){ if (index >= 0 && index < static_cast(childOffsets.size())) { childOffsets[index] = pos; - needRedraw = true; - activeRedrawFrames = kDefaultActiveFrames; + markDirty(); } } From 93b7c065e6f65382b0c9d50831f23fb9995fb68a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:51:01 +0000 Subject: [PATCH 10/11] docs: explain panel redraw window constant Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- include/Widget.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Widget.h b/include/Widget.h index e4901e9..a84de9e 100644 --- a/include/Widget.h +++ b/include/Widget.h @@ -209,6 +209,7 @@ class Panel : public Widget { bool scaleChanged = true; bool needRedraw = true; int activeRedrawFrames = 0; + // Keep redraws alive for ~80 frames after interactions (covers ripple等动画约1s @60fps) static constexpr int kDefaultActiveFrames = 80; std::vector children; From ad6d2a4708f23110bd25299be148e13e0cc2d6b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:52:17 +0000 Subject: [PATCH 11/11] chore: clarify panel redraw window handling Co-authored-by: FeJS8888 <110683147+FeJS8888@users.noreply.github.com> --- include/Widget.h | 2 +- src/Widget.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Widget.h b/include/Widget.h index a84de9e..97ecd97 100644 --- a/include/Widget.h +++ b/include/Widget.h @@ -209,7 +209,7 @@ class Panel : public Widget { bool scaleChanged = true; bool needRedraw = true; int activeRedrawFrames = 0; - // Keep redraws alive for ~80 frames after interactions (covers ripple等动画约1s @60fps) + // Keep redraws alive for ~80 frames after interactions (covers ripple/transition ~1s @60fps) static constexpr int kDefaultActiveFrames = 80; std::vector children; diff --git a/src/Widget.cpp b/src/Widget.cpp index 2d89004..340cfc7 100644 --- a/src/Widget.cpp +++ b/src/Widget.cpp @@ -68,6 +68,7 @@ void Panel::draw() { } void Panel::draw(PIMAGE dst, double x, double y) { + // activeRedrawFrames keeps a short redraw window after interactions/animations bool shouldRedraw = needRedraw || scaleChanged || PanelScaleChanged || activeRedrawFrames > 0; if (layout && shouldRedraw) layout->apply(*this); // 自动计算子控件位置 @@ -99,8 +100,8 @@ void Panel::draw(PIMAGE dst, double x, double y) { } PanelScaleChanged = false; scaleChanged = false; - needRedraw = false; if (activeRedrawFrames > 0) activeRedrawFrames--; + needRedraw = activeRedrawFrames > 0; // 生成一次遮罩后的缓存层,后续直接用 putimage_withalpha setbkcolor_f(EGEARGB(0, 0, 0, 0), cachedLayer);