发布前托管重绘与布局封版收口
收口 Dialog/overlay 后鼠标状态同步、Tooltip 临时 coverage 与持久 coverage 拆分、跨 root 脏区补提交、TextBox/Button 绘制副作用修复,并补充 KEY6 回归用例和 BUG/Fix/Feature 开发记录。
This commit is contained in:
+14
-7
@@ -173,11 +173,13 @@ void Button::draw()
|
|||||||
|
|
||||||
//保存当前样式和颜色
|
//保存当前样式和颜色
|
||||||
saveStyle();
|
saveStyle();
|
||||||
|
StellarX::ControlText drawTextStyle = textStyle;
|
||||||
|
|
||||||
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
|
if (StellarX::ButtonMode::DISABLED == mode) //设置禁用按钮色
|
||||||
{
|
{
|
||||||
setfillcolor(DISABLEDCOLOUR);
|
setfillcolor(DISABLEDCOLOUR);
|
||||||
textStyle.bStrikeOut = true;
|
// 禁用态删除线只属于本次绘制效果,不回写用户公开 textStyle。
|
||||||
|
drawTextStyle.bStrikeOut = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -192,11 +194,11 @@ void Button::draw()
|
|||||||
setlinecolor(buttonBorderColor);
|
setlinecolor(buttonBorderColor);
|
||||||
|
|
||||||
//设置字体颜色
|
//设置字体颜色
|
||||||
settextcolor(textStyle.color);
|
settextcolor(drawTextStyle.color);
|
||||||
//设置字体样式
|
//设置字体样式
|
||||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
settextstyle(drawTextStyle.nHeight, drawTextStyle.nWidth, drawTextStyle.lpszFace,
|
||||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
drawTextStyle.nEscapement, drawTextStyle.nOrientation, drawTextStyle.nWeight,
|
||||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
drawTextStyle.bItalic, drawTextStyle.bUnderline, drawTextStyle.bStrikeOut);
|
||||||
|
|
||||||
if (needCutText)
|
if (needCutText)
|
||||||
cutButtonText();
|
cutButtonText();
|
||||||
@@ -526,6 +528,13 @@ RECT Button::getManagedRepaintCoverageRect() const
|
|||||||
return coverage;
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT Button::getManagedRepaintPersistentCoverageRect() const
|
||||||
|
{
|
||||||
|
// Tooltip 属于临时浮层,只参与完整 coverage 和上层 overlay 判断;
|
||||||
|
// 兄弟控件是否需要作废背景快照,只应由按钮本体这类持久绘制范围决定。
|
||||||
|
return getBoundsRect();
|
||||||
|
}
|
||||||
|
|
||||||
void Button::setOnClickListener(std::function<void()> callback)
|
void Button::setOnClickListener(std::function<void()> callback)
|
||||||
{
|
{
|
||||||
this->onClickCallback = std::move(callback);
|
this->onClickCallback = std::move(callback);
|
||||||
@@ -542,8 +551,6 @@ void Button::setOnToggleOffListener(std::function<void()> callback)
|
|||||||
|
|
||||||
void Button::setbuttonMode(StellarX::ButtonMode mode)
|
void Button::setbuttonMode(StellarX::ButtonMode mode)
|
||||||
{
|
{
|
||||||
if (this->mode == StellarX::ButtonMode::DISABLED && mode != StellarX::ButtonMode::DISABLED)
|
|
||||||
textStyle.bStrikeOut = false;
|
|
||||||
//取值范围参考 buttMode的枚举注释
|
//取值范围参考 buttMode的枚举注释
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
dirty = true; // 标记需要重绘
|
dirty = true; // 标记需要重绘
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ public:
|
|||||||
bool clearTransientMouseState() override;
|
bool clearTransientMouseState() override;
|
||||||
// Tooltip 可见时,按钮实际写像素范围不再等于按钮本体,需要把 Tooltip 矩形并入 coverage。
|
// Tooltip 可见时,按钮实际写像素范围不再等于按钮本体,需要把 Tooltip 矩形并入 coverage。
|
||||||
RECT getManagedRepaintCoverageRect() const override;
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
|
// Button 的持久绘制范围只包含本体;Tooltip 是临时浮层,不能污染兄弟控件背景快照。
|
||||||
|
RECT getManagedRepaintPersistentCoverageRect() const override;
|
||||||
|
|
||||||
// 设置 NORMAL 模式下的点击回调
|
// 设置 NORMAL 模式下的点击回调
|
||||||
void setOnClickListener(std::function<void()> callback);
|
void setOnClickListener(std::function<void()> callback);
|
||||||
@@ -134,7 +136,7 @@ public:
|
|||||||
void setButtonText(std::string text);
|
void setButtonText(std::string text);
|
||||||
// 设置按钮几何形状
|
// 设置按钮几何形状
|
||||||
void setButtonShape(StellarX::ControlShape shape);
|
void setButtonShape(StellarX::ControlShape shape);
|
||||||
// 直接设置按钮点击状态;TOGGLE 模式下会按状态变化触发相应回调
|
// 直接设置按钮点击状态;NORMAL 设置为 true 时触发 onClick,TOGGLE 仅在状态变化时触发 on/off 回调
|
||||||
void setButtonClick(BOOL click);
|
void setButtonClick(BOOL click);
|
||||||
|
|
||||||
// 查询按钮当前是否处于点击/选中状态
|
// 查询按钮当前是否处于点击/选中状态
|
||||||
@@ -168,7 +170,7 @@ public:
|
|||||||
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
||||||
// 设置 Tooltip 是否跟随鼠标
|
// 设置 Tooltip 是否跟随鼠标
|
||||||
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
||||||
// 设置 Tooltip 相对鼠标/按钮的偏移量
|
// 设置 Tooltip 偏移量;当前仅在 setTooltipFollowCursor(true) 时参与定位
|
||||||
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
||||||
// 设置 Tooltip 的文字、背景和透明样式
|
// 设置 Tooltip 的文字、背景和透明样式
|
||||||
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
||||||
|
|||||||
+61
-14
@@ -22,6 +22,12 @@ static const char* SxCanvasMsgName(UINT m)
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
enum class SxCanvasOverlayRedrawMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
RefreshSnapshot
|
||||||
|
};
|
||||||
|
|
||||||
bool SxCanvasRectValid(const RECT& rc)
|
bool SxCanvasRectValid(const RECT& rc)
|
||||||
{
|
{
|
||||||
return rc.right > rc.left && rc.bottom > rc.top;
|
return rc.right > rc.left && rc.bottom > rc.top;
|
||||||
@@ -209,12 +215,21 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
|||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (c->clearTransientMouseState())
|
else
|
||||||
{
|
{
|
||||||
|
// 后续兄弟只走临时状态清理,不会再进入自己的 handleEvent()。
|
||||||
|
// Tooltip 隐藏会先回贴旧快照,再改变 coverage;因此必须先保存旧覆盖范围,
|
||||||
|
// 避免登记重绘时丢失旧 Tooltip 区域,导致上层 overlay 补画判断不完整。
|
||||||
|
const RECT previousCoverage = c->getManagedRepaintCoverageRect();
|
||||||
|
if (c->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
if (Window* host = getHostWindow())
|
||||||
|
host->requestManagedRepaint(c, previousCoverage);
|
||||||
anyVisualChanged = true;
|
anyVisualChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
@@ -403,9 +418,11 @@ void Canvas::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
SX_LOG_TRACE("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
|
SX_LOG_TRACE("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id;
|
||||||
|
|
||||||
RECT coverage{};
|
RECT paintCoverage{};
|
||||||
bool hasCoverage = false;
|
bool hasPaintCoverage = false;
|
||||||
auto commitManagedChild = [&](Control* child, bool forceOverlayRedraw)
|
RECT persistentCoverage{};
|
||||||
|
bool hasPersistentCoverage = false;
|
||||||
|
auto commitManagedChild = [&](Control* child, SxCanvasOverlayRedrawMode overlayMode)
|
||||||
{
|
{
|
||||||
if (!child || !child->IsVisible())
|
if (!child || !child->IsVisible())
|
||||||
return;
|
return;
|
||||||
@@ -413,10 +430,10 @@ void Canvas::requestRepaint(Control* parent)
|
|||||||
const bool directDirty = child->isDirty();
|
const bool directDirty = child->isDirty();
|
||||||
const bool subtreeDirty = child->hasManagedDirtySubtree();
|
const bool subtreeDirty = child->hasManagedDirtySubtree();
|
||||||
|
|
||||||
if (forceOverlayRedraw)
|
if (overlayMode == SxCanvasOverlayRedrawMode::RefreshSnapshot)
|
||||||
{
|
{
|
||||||
// overlay 补画必须先作废旧快照:
|
// overlay 补画必须先作废旧快照:
|
||||||
// 下层兄弟刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。
|
// 下层兄弟的持久内容刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。
|
||||||
child->invalidateBackgroundSnapshot();
|
child->invalidateBackgroundSnapshot();
|
||||||
child->setDirty(true);
|
child->setDirty(true);
|
||||||
child->draw();
|
child->draw();
|
||||||
@@ -437,15 +454,26 @@ void Canvas::requestRepaint(Control* parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RECT childRect = child->getManagedRepaintCoverageRect();
|
const RECT childPaintRect = child->getManagedRepaintCoverageRect();
|
||||||
if (!hasCoverage)
|
if (!hasPaintCoverage)
|
||||||
{
|
{
|
||||||
coverage = childRect;
|
paintCoverage = childPaintRect;
|
||||||
hasCoverage = true;
|
hasPaintCoverage = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
coverage = SxCanvasUnionRect(coverage, childRect);
|
paintCoverage = SxCanvasUnionRect(paintCoverage, childPaintRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT childPersistentRect = child->getManagedRepaintPersistentCoverageRect();
|
||||||
|
if (!hasPersistentCoverage)
|
||||||
|
{
|
||||||
|
persistentCoverage = childPersistentRect;
|
||||||
|
hasPersistentCoverage = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
persistentCoverage = SxCanvasUnionRect(persistentCoverage, childPersistentRect);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -457,12 +485,17 @@ void Canvas::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
if (child->hasManagedDirtySubtree())
|
if (child->hasManagedDirtySubtree())
|
||||||
{
|
{
|
||||||
commitManagedChild(child, false);
|
commitManagedChild(child, SxCanvasOverlayRedrawMode::None);
|
||||||
}
|
}
|
||||||
else if (hasCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), coverage))
|
else if (hasPaintCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), paintCoverage))
|
||||||
{
|
{
|
||||||
// 位于本次累计 coverage 上方、且发生相交的兄弟控件,需要补画回最上层。
|
// 位于本次累计 coverage 上方、且发生相交的兄弟控件,需要补画回最上层。
|
||||||
commitManagedChild(child, true);
|
// 但只有下层“持久内容”影响到它时,才允许作废并重新抓背景快照;
|
||||||
|
// 如果只是被 Tooltip 等临时浮层覆盖,则跳过兄弟补画,避免透明控件回贴旧快照擦掉 Tooltip。
|
||||||
|
const bool persistentHit = hasPersistentCoverage &&
|
||||||
|
SxCanvasRectsIntersect(child->getManagedRepaintPersistentCoverageRect(), persistentCoverage);
|
||||||
|
if (persistentHit)
|
||||||
|
commitManagedChild(child, SxCanvasOverlayRedrawMode::RefreshSnapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +535,20 @@ RECT Canvas::getManagedRepaintCoverageRect() const
|
|||||||
return coverage;
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT Canvas::getManagedRepaintPersistentCoverageRect() const
|
||||||
|
{
|
||||||
|
// 持久 coverage 只描述会进入背景快照语义的范围。
|
||||||
|
// 子控件 Tooltip 等临时浮层不会被并入,避免兄弟控件补画时抓到临时像素。
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
for (const auto& child : controls)
|
||||||
|
{
|
||||||
|
if (!child->IsVisible())
|
||||||
|
continue;
|
||||||
|
coverage = SxCanvasUnionRect(coverage, child->getManagedRepaintPersistentCoverageRect());
|
||||||
|
}
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
bool Canvas::canCommitManagedPartialRepaint() const
|
bool Canvas::canCommitManagedPartialRepaint() const
|
||||||
{
|
{
|
||||||
// Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时,
|
// Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public:
|
|||||||
void requestRepaint(Control* parent)override;
|
void requestRepaint(Control* parent)override;
|
||||||
bool hasManagedDirtySubtree() const override;
|
bool hasManagedDirtySubtree() const override;
|
||||||
RECT getManagedRepaintCoverageRect() const override;
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
|
RECT getManagedRepaintPersistentCoverageRect() const override;
|
||||||
// 判断当前 Canvas 是否可安全做局部提交
|
// 判断当前 Canvas 是否可安全做局部提交
|
||||||
bool canCommitManagedPartialRepaint() const override;
|
bool canCommitManagedPartialRepaint() const override;
|
||||||
// 托管收口阶段执行 Canvas 的真正重绘
|
// 托管收口阶段执行 Canvas 的真正重绘
|
||||||
|
|||||||
@@ -579,6 +579,13 @@ RECT Control::getManagedRepaintCoverageRect() const
|
|||||||
return getBoundsRect();
|
return getBoundsRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT Control::getManagedRepaintPersistentCoverageRect() const
|
||||||
|
{
|
||||||
|
// 基类默认认为持久绘制范围等于控件本体。
|
||||||
|
// 只有 Tooltip 这类临时浮层需要从持久范围中剔除。
|
||||||
|
return getBoundsRect();
|
||||||
|
}
|
||||||
|
|
||||||
bool Control::canCommitManagedPartialRepaint() const
|
bool Control::canCommitManagedPartialRepaint() const
|
||||||
{
|
{
|
||||||
// 基类默认不承诺自己能安全做局部提交;
|
// 基类默认不承诺自己能安全做局部提交;
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ public:
|
|||||||
// 默认等于控件本体矩形;像 Button 这类会额外绘制 Tooltip 的控件,可 override 后扩大范围。
|
// 默认等于控件本体矩形;像 Button 这类会额外绘制 Tooltip 的控件,可 override 后扩大范围。
|
||||||
// 托管重绘 coverage、overlay 相交判断统一走这个接口,而不再默认使用控件本体 bounds。
|
// 托管重绘 coverage、overlay 相交判断统一走这个接口,而不再默认使用控件本体 bounds。
|
||||||
virtual RECT getManagedRepaintCoverageRect() const;
|
virtual RECT getManagedRepaintCoverageRect() const;
|
||||||
|
// 获取会影响后续控件背景快照的“持久绘制范围”。
|
||||||
|
// Tooltip 等临时浮层不应进入该范围,避免上层兄弟补画时把临时浮层抓进背景快照。
|
||||||
|
virtual RECT getManagedRepaintPersistentCoverageRect() const;
|
||||||
Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root
|
Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root
|
||||||
Control* getManagedRepaintDirectBranch(Control* root); // 找到“root 下面承接本次脏变化的直接子分支”
|
Control* getManagedRepaintDirectBranch(Control* root); // 找到“root 下面承接本次脏变化的直接子分支”
|
||||||
bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照
|
bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#define BorderWidth 3 //边框宽度
|
#define BorderWidth 3 //边框宽度
|
||||||
class Dialog : public Canvas
|
class Dialog : public Canvas
|
||||||
{
|
{
|
||||||
|
friend class Window;
|
||||||
|
|
||||||
Window& hWnd; //窗口引用
|
Window& hWnd; //窗口引用
|
||||||
|
|
||||||
int textWidth = 0; //文本宽度
|
int textWidth = 0; //文本宽度
|
||||||
|
|||||||
+6
-8
@@ -1,13 +1,13 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @文件: StellarX.h
|
* @文件: StellarX.h
|
||||||
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
* @摘要: 星垣(StellarX) GUI框架 - 主包含头文件
|
||||||
* @版本: v3.0.2
|
* @版本: v3.1.0
|
||||||
* @描述:
|
* @描述:
|
||||||
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
* 一个为Windows平台打造的轻量级、模块化C++ GUI框架。
|
||||||
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
* 基于EasyX图形库,提供简洁易用的API和丰富的控件。
|
||||||
*
|
*
|
||||||
* 通过包含此单一头文件,即可使用框架的所有功能。
|
* 通过包含此单一头文件,即可使用框架的所有功能。
|
||||||
* 内部包含顺序经过精心设计,确保所有依赖关系正确解析。
|
* 内部头文件包含顺序由框架维护,用户代码不应依赖该顺序。
|
||||||
*
|
*
|
||||||
* @作者: 我在人间做废物
|
* @作者: 我在人间做废物
|
||||||
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
* @邮箱: [3150131407@qq.com] | [ysm3150131407@gmail.com]
|
||||||
@@ -21,12 +21,10 @@
|
|||||||
* @使用说明:
|
* @使用说明:
|
||||||
* 只需包含此文件即可使用框架所有功能。
|
* 只需包含此文件即可使用框架所有功能。
|
||||||
* 示例: #include "StellarX.h"
|
* 示例: #include "StellarX.h"
|
||||||
* @包含顺序:
|
* @包含模块:
|
||||||
* 1. CoreTypes.h - 基础类型定义
|
* CoreTypes.h / SxLog.h / Control.h / Canvas.h / Window.h
|
||||||
* 2. Control.h - 控件基类
|
* Button.h / Label.h / TextBox.h / Table.h
|
||||||
* 3. ...其他具体控件头文件
|
* Dialog.h / MessageBox.h / TabControl.h
|
||||||
* 4. Dialog:继承自 Canvas(Dialog 为可包含子控件的对话框容器)
|
|
||||||
* 5. MessageBox:对话框工厂,提供便捷的模态/非模态调用方式
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|||||||
+57
-17
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
enum class SxTabOverlayRedrawMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
RefreshSnapshot
|
||||||
|
};
|
||||||
|
|
||||||
bool SxTabRectsIntersect(const RECT& a, const RECT& b)
|
bool SxTabRectsIntersect(const RECT& a, const RECT& b)
|
||||||
{
|
{
|
||||||
return a.left < b.right && a.right > b.left &&
|
return a.left < b.right && a.right > b.left &&
|
||||||
@@ -473,7 +479,7 @@ int TabControl::indexOf(const std::string& tabText) const
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
return idx;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabControl::setDirty(bool dirty)
|
void TabControl::setDirty(bool dirty)
|
||||||
@@ -498,9 +504,11 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
if (this == parent)
|
if (this == parent)
|
||||||
{
|
{
|
||||||
RECT coverage{};
|
RECT paintCoverage{};
|
||||||
bool hasCoverage = false;
|
bool hasPaintCoverage = false;
|
||||||
auto commitTabUnit = [&](Control* unit, bool forceOverlayRedraw)
|
RECT persistentCoverage{};
|
||||||
|
bool hasPersistentCoverage = false;
|
||||||
|
auto commitTabUnit = [&](Control* unit, SxTabOverlayRedrawMode overlayMode)
|
||||||
{
|
{
|
||||||
if (!unit || !unit->IsVisible())
|
if (!unit || !unit->IsVisible())
|
||||||
return;
|
return;
|
||||||
@@ -508,9 +516,9 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
const bool directDirty = unit->isDirty();
|
const bool directDirty = unit->isDirty();
|
||||||
const bool subtreeDirty = unit->hasManagedDirtySubtree();
|
const bool subtreeDirty = unit->hasManagedDirtySubtree();
|
||||||
|
|
||||||
if (forceOverlayRedraw)
|
if (overlayMode == SxTabOverlayRedrawMode::RefreshSnapshot)
|
||||||
{
|
{
|
||||||
// 下层单元已经写过像素,上层页签/页面作为 overlay 补画时,
|
// 下层单元的持久内容已经写过像素,上层页签/页面作为 overlay 补画时,
|
||||||
// 必须先丢掉旧快照,重新抓取当前背景后再画,否则会把旧背景再贴回来。
|
// 必须先丢掉旧快照,重新抓取当前背景后再画,否则会把旧背景再贴回来。
|
||||||
unit->invalidateBackgroundSnapshot();
|
unit->invalidateBackgroundSnapshot();
|
||||||
unit->setDirty(true);
|
unit->setDirty(true);
|
||||||
@@ -529,15 +537,26 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RECT rc = unit->getManagedRepaintCoverageRect();
|
const RECT paintRect = unit->getManagedRepaintCoverageRect();
|
||||||
if (!hasCoverage)
|
if (!hasPaintCoverage)
|
||||||
{
|
{
|
||||||
coverage = rc;
|
paintCoverage = paintRect;
|
||||||
hasCoverage = true;
|
hasPaintCoverage = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
coverage = SxTabUnionRect(coverage, rc);
|
paintCoverage = SxTabUnionRect(paintCoverage, paintRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT persistentRect = unit->getManagedRepaintPersistentCoverageRect();
|
||||||
|
if (!hasPersistentCoverage)
|
||||||
|
{
|
||||||
|
persistentCoverage = persistentRect;
|
||||||
|
hasPersistentCoverage = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
persistentCoverage = SxTabUnionRect(persistentCoverage, persistentRect);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -553,11 +572,14 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
if (page->hasManagedDirtySubtree())
|
if (page->hasManagedDirtySubtree())
|
||||||
{
|
{
|
||||||
commitTabUnit(page, false);
|
commitTabUnit(page, SxTabOverlayRedrawMode::None);
|
||||||
}
|
}
|
||||||
else if (hasCoverage && SxTabRectsIntersect(page->getManagedRepaintCoverageRect(), coverage))
|
else if (hasPaintCoverage && SxTabRectsIntersect(page->getManagedRepaintCoverageRect(), paintCoverage))
|
||||||
{
|
{
|
||||||
commitTabUnit(page, true);
|
const bool persistentHit = hasPersistentCoverage &&
|
||||||
|
SxTabRectsIntersect(page->getManagedRepaintPersistentCoverageRect(), persistentCoverage);
|
||||||
|
if (persistentHit)
|
||||||
|
commitTabUnit(page, SxTabOverlayRedrawMode::RefreshSnapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,11 +591,14 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
if (button->hasManagedDirtySubtree())
|
if (button->hasManagedDirtySubtree())
|
||||||
{
|
{
|
||||||
commitTabUnit(button, false);
|
commitTabUnit(button, SxTabOverlayRedrawMode::None);
|
||||||
}
|
}
|
||||||
else if (hasCoverage && SxTabRectsIntersect(button->getManagedRepaintCoverageRect(), coverage))
|
else if (hasPaintCoverage && SxTabRectsIntersect(button->getManagedRepaintCoverageRect(), paintCoverage))
|
||||||
{
|
{
|
||||||
commitTabUnit(button, true);
|
const bool persistentHit = hasPersistentCoverage &&
|
||||||
|
SxTabRectsIntersect(button->getManagedRepaintPersistentCoverageRect(), persistentCoverage);
|
||||||
|
if (persistentHit)
|
||||||
|
commitTabUnit(button, SxTabOverlayRedrawMode::RefreshSnapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -612,6 +637,21 @@ RECT TabControl::getManagedRepaintCoverageRect() const
|
|||||||
return coverage;
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT TabControl::getManagedRepaintPersistentCoverageRect() const
|
||||||
|
{
|
||||||
|
// 持久 coverage 排除页签按钮 Tooltip 等临时浮层,
|
||||||
|
// 用于判断上层页签/页面补画时是否允许刷新背景快照。
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
for (const auto& control : controls)
|
||||||
|
{
|
||||||
|
if (control.first->IsVisible())
|
||||||
|
coverage = SxTabUnionRect(coverage, control.first->getManagedRepaintPersistentCoverageRect());
|
||||||
|
if (control.second->IsVisible())
|
||||||
|
coverage = SxTabUnionRect(coverage, control.second->getManagedRepaintPersistentCoverageRect());
|
||||||
|
}
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
bool TabControl::canCommitManagedPartialRepaint() const
|
bool TabControl::canCommitManagedPartialRepaint() const
|
||||||
{
|
{
|
||||||
// TabControl 只有在自己本体不脏且背景快照有效时,才允许只更新脏页签/脏页面。
|
// TabControl 只有在自己本体不脏且背景快照有效时,才允许只更新脏页签/脏页面。
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ public:
|
|||||||
void requestRepaint(Control* parent)override; // 托管模式下登记为 root;非托管模式下局部更新脏按钮/脏页面
|
void requestRepaint(Control* parent)override; // 托管模式下登记为 root;非托管模式下局部更新脏按钮/脏页面
|
||||||
bool hasManagedDirtySubtree() const override;
|
bool hasManagedDirtySubtree() const override;
|
||||||
RECT getManagedRepaintCoverageRect() const override;
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
|
RECT getManagedRepaintPersistentCoverageRect() const override;
|
||||||
bool canCommitManagedPartialRepaint() const override; // 判断当前 TabControl 是否可安全做局部提交
|
bool canCommitManagedPartialRepaint() const override; // 判断当前 TabControl 是否可安全做局部提交
|
||||||
void commitManagedRepaint() override; // 托管收口阶段执行 TabControl 的真正重绘
|
void commitManagedRepaint() override; // 托管收口阶段执行 TabControl 的真正重绘
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -614,6 +614,40 @@ RECT Table::getManagedRepaintCoverageRect() const
|
|||||||
return coverage;
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT Table::getManagedRepaintPersistentCoverageRect() const
|
||||||
|
{
|
||||||
|
// Table 当前仍按整表绘制保证正确性;分页按钮 Tooltip 属于临时浮层,
|
||||||
|
// 不能进入持久 coverage,否则可能污染外层兄弟控件快照。
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
|
||||||
|
if (pageNum && pageNum->IsVisible())
|
||||||
|
{
|
||||||
|
const RECT pageRect = pageNum->getManagedRepaintPersistentCoverageRect();
|
||||||
|
coverage.left = (std::min)(coverage.left, pageRect.left);
|
||||||
|
coverage.top = (std::min)(coverage.top, pageRect.top);
|
||||||
|
coverage.right = (std::max)(coverage.right, pageRect.right);
|
||||||
|
coverage.bottom = (std::max)(coverage.bottom, pageRect.bottom);
|
||||||
|
}
|
||||||
|
if (prevButton && prevButton->IsVisible())
|
||||||
|
{
|
||||||
|
const RECT prevRect = prevButton->getManagedRepaintPersistentCoverageRect();
|
||||||
|
coverage.left = (std::min)(coverage.left, prevRect.left);
|
||||||
|
coverage.top = (std::min)(coverage.top, prevRect.top);
|
||||||
|
coverage.right = (std::max)(coverage.right, prevRect.right);
|
||||||
|
coverage.bottom = (std::max)(coverage.bottom, prevRect.bottom);
|
||||||
|
}
|
||||||
|
if (nextButton && nextButton->IsVisible())
|
||||||
|
{
|
||||||
|
const RECT nextRect = nextButton->getManagedRepaintPersistentCoverageRect();
|
||||||
|
coverage.left = (std::min)(coverage.left, nextRect.left);
|
||||||
|
coverage.top = (std::min)(coverage.top, nextRect.top);
|
||||||
|
coverage.right = (std::max)(coverage.right, nextRect.right);
|
||||||
|
coverage.bottom = (std::max)(coverage.bottom, nextRect.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
void Table::setHeaders(std::initializer_list<std::string> headers)
|
void Table::setHeaders(std::initializer_list<std::string> headers)
|
||||||
{
|
{
|
||||||
this->headers.clear();
|
this->headers.clear();
|
||||||
|
|||||||
@@ -122,12 +122,14 @@ public:
|
|||||||
bool clearTransientMouseState() override;
|
bool clearTransientMouseState() override;
|
||||||
// Table 重绘时会一并绘制页码 Label 和分页按钮,coverage 需要把这些内部绘制单元并入。
|
// Table 重绘时会一并绘制页码 Label 和分页按钮,coverage 需要把这些内部绘制单元并入。
|
||||||
RECT getManagedRepaintCoverageRect() const override;
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
|
// 持久 coverage 排除分页按钮 Tooltip 等临时浮层。
|
||||||
|
RECT getManagedRepaintPersistentCoverageRect() const override;
|
||||||
|
|
||||||
// 设置表头
|
// 设置表头
|
||||||
void setHeaders(std::initializer_list<std::string> headers);
|
void setHeaders(std::initializer_list<std::string> headers);
|
||||||
// 设置表格数据(单行追加)
|
// 设置表格数据(单行追加)
|
||||||
void setData(std::vector<std::string> data);
|
void setData(std::vector<std::string> data);
|
||||||
// 设置表格数据(多行覆盖)
|
// 设置表格数据(多行追加);如需覆盖请先 clearData() 或 resetTable()
|
||||||
void setData(std::initializer_list<std::vector<std::string>> data);
|
void setData(std::initializer_list<std::vector<std::string>> data);
|
||||||
//设置每页显示的行数
|
//设置每页显示的行数
|
||||||
void setRowsPerPage(int rows);
|
void setRowsPerPage(int rows);
|
||||||
|
|||||||
+111
-21
@@ -2,6 +2,105 @@
|
|||||||
#include "TextBox.h"
|
#include "TextBox.h"
|
||||||
#include "SxLog.h"
|
#include "SxLog.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int SxTextBoxMbcCharLen(const std::string& s, size_t i)
|
||||||
|
{
|
||||||
|
unsigned char b = static_cast<unsigned char>(s[i]);
|
||||||
|
if (b <= 0x7F)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (b >= 0x81 && b <= 0xFE && i + 1 < s.size())
|
||||||
|
{
|
||||||
|
unsigned char b2 = static_cast<unsigned char>(s[i + 1]);
|
||||||
|
if (b2 >= 0x40 && b2 <= 0xFE && b2 != 0x7F)
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非法或不完整字节按单字节容错,避免死循环。
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SxTextBoxTrimTrailingSpaces(std::string& s)
|
||||||
|
{
|
||||||
|
while (!s.empty() && s.back() == ' ')
|
||||||
|
s.pop_back();
|
||||||
|
|
||||||
|
while (s.size() >= 2)
|
||||||
|
{
|
||||||
|
unsigned char a = static_cast<unsigned char>(s[s.size() - 2]);
|
||||||
|
unsigned char b = static_cast<unsigned char>(s[s.size() - 1]);
|
||||||
|
if (a == 0xA1 && b == 0xA1)
|
||||||
|
s.resize(s.size() - 2);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SxTextBoxTruncateByMbcBoundary(const std::string& text, size_t maxBytes)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
size_t lastSafe = 0;
|
||||||
|
while (i < text.size())
|
||||||
|
{
|
||||||
|
const int charLen = SxTextBoxMbcCharLen(text, i);
|
||||||
|
const size_t next = i + static_cast<size_t>(charLen);
|
||||||
|
if (next > maxBytes || next > text.size())
|
||||||
|
break;
|
||||||
|
lastSafe = next;
|
||||||
|
i = next;
|
||||||
|
}
|
||||||
|
return text.substr(0, lastSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SxTextBoxEllipsizeMbc(const std::string& text, int maxWidth)
|
||||||
|
{
|
||||||
|
if (maxWidth <= 0)
|
||||||
|
return "";
|
||||||
|
if (textwidth(LPCTSTR(text.c_str())) <= maxWidth)
|
||||||
|
return text;
|
||||||
|
|
||||||
|
const std::string ellipsis = "...";
|
||||||
|
const int ellipsisWidth = textwidth(LPCTSTR(ellipsis.c_str()));
|
||||||
|
if (ellipsisWidth > maxWidth)
|
||||||
|
{
|
||||||
|
std::string clippedEllipsis = ellipsis;
|
||||||
|
while (!clippedEllipsis.empty() && textwidth(LPCTSTR(clippedEllipsis.c_str())) > maxWidth)
|
||||||
|
clippedEllipsis.pop_back();
|
||||||
|
return clippedEllipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int contentLimit = maxWidth - ellipsisWidth;
|
||||||
|
size_t i = 0;
|
||||||
|
size_t lastFit = 0;
|
||||||
|
while (i < text.size())
|
||||||
|
{
|
||||||
|
const int charLen = SxTextBoxMbcCharLen(text, i);
|
||||||
|
const size_t next = i + static_cast<size_t>(charLen);
|
||||||
|
if (next > text.size())
|
||||||
|
break;
|
||||||
|
|
||||||
|
const std::string candidate = text.substr(0, next);
|
||||||
|
if (textwidth(LPCTSTR(candidate.c_str())) <= contentLimit)
|
||||||
|
{
|
||||||
|
lastFit = next;
|
||||||
|
i = next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastFit == 0)
|
||||||
|
return ellipsis;
|
||||||
|
|
||||||
|
std::string head = text.substr(0, lastFit);
|
||||||
|
SxTextBoxTrimTrailingSpaces(head);
|
||||||
|
return head + ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
|
TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX::TextBoxmode mode, StellarX::ControlShape shape)
|
||||||
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
||||||
{
|
{
|
||||||
@@ -20,22 +119,22 @@ void TextBox::draw()
|
|||||||
saveStyle();
|
saveStyle();
|
||||||
setfillcolor(textBoxBkClor);
|
setfillcolor(textBoxBkClor);
|
||||||
setlinecolor(textBoxBorderClor);
|
setlinecolor(textBoxBorderClor);
|
||||||
if (textStyle.nHeight > height)
|
StellarX::ControlText drawStyle = textStyle;
|
||||||
textStyle.nHeight = height;
|
if (drawStyle.nHeight > height)
|
||||||
if (textStyle.nWidth > width)
|
drawStyle.nHeight = height;
|
||||||
textStyle.nWidth = width;
|
if (drawStyle.nWidth > width)
|
||||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
drawStyle.nWidth = width;
|
||||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
settextstyle(drawStyle.nHeight, drawStyle.nWidth, drawStyle.lpszFace,
|
||||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut);
|
drawStyle.nEscapement, drawStyle.nOrientation, drawStyle.nWeight,
|
||||||
|
drawStyle.bItalic, drawStyle.bUnderline, drawStyle.bStrikeOut);
|
||||||
|
|
||||||
settextcolor(textStyle.color);
|
settextcolor(drawStyle.color);
|
||||||
setbkmode(TRANSPARENT);
|
setbkmode(TRANSPARENT);
|
||||||
|
|
||||||
int text_width = 0;
|
int text_width = 0;
|
||||||
int text_height = 0;
|
int text_height = 0;
|
||||||
std::string pwdText;
|
std::string pwdText;
|
||||||
std::string displayText; // 用于显示的文本(可能被截断)
|
std::string displayText; // 用于显示的文本(可能被截断)
|
||||||
bool isTextTruncated = false; // 标记文本是否被截断
|
|
||||||
|
|
||||||
if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
if (StellarX::TextBoxmode::PASSWORD_MODE == mode)
|
||||||
{
|
{
|
||||||
@@ -55,17 +154,8 @@ void TextBox::draw()
|
|||||||
int currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
int currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
||||||
if (currentWidth > availableWidth && availableWidth > 0)
|
if (currentWidth > availableWidth && availableWidth > 0)
|
||||||
{
|
{
|
||||||
// 需要截断文本,预留空间放置省略号
|
// 按 GBK/MBCS 字符边界裁切,避免中文文本被截断到半个字节。
|
||||||
int ellipsisWidth = textwidth("...");
|
displayText = SxTextBoxEllipsizeMbc(displayText, availableWidth);
|
||||||
int truncatedWidth = availableWidth - ellipsisWidth;
|
|
||||||
|
|
||||||
std::string truncatedText = displayText;
|
|
||||||
while (truncatedText.size() > 0 && textwidth(LPCTSTR(truncatedText.c_str())) > truncatedWidth)
|
|
||||||
{
|
|
||||||
truncatedText.pop_back();
|
|
||||||
}
|
|
||||||
displayText = truncatedText + "...";
|
|
||||||
isTextTruncated = true;
|
|
||||||
currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
currentWidth = textwidth(LPCTSTR(displayText.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +321,7 @@ void TextBox::setText(std::string text)
|
|||||||
if(text == this->text)
|
if(text == this->text)
|
||||||
return; // 文本未改变,无需更新和重绘
|
return; // 文本未改变,无需更新和重绘
|
||||||
if (text.size() > maxCharLen)
|
if (text.size() > maxCharLen)
|
||||||
text = text.substr(0, maxCharLen);
|
text = SxTextBoxTruncateByMbcBoundary(text, maxCharLen);
|
||||||
this->text = text;
|
this->text = text;
|
||||||
this->dirty = true; // 标记需要重绘,不论是否窗口图形上下文是否已初始化,等第一次绘制时由窗口真正调用 draw() 来重绘显示文本
|
this->dirty = true; // 标记需要重绘,不论是否窗口图形上下文是否已初始化,等第一次绘制时由窗口真正调用 draw() 来重绘显示文本
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ public:
|
|||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
// 设置文本框模式
|
// 设置文本框模式
|
||||||
void setMode(StellarX::TextBoxmode mode);
|
void setMode(StellarX::TextBoxmode mode);
|
||||||
// 设置可输入最大字符长度
|
// 设置可输入最大长度;当前按 std::string 字节长度限制,不等同于 Unicode 字符数
|
||||||
void setMaxCharLen(size_t len);
|
void setMaxCharLen(size_t len);
|
||||||
// 设置文本框形状
|
// 设置文本框形状;仅支持矩形/圆角矩形,圆形和椭圆会回退为 RECTANGLE
|
||||||
void setTextBoxshape(StellarX::ControlShape shape);
|
void setTextBoxshape(StellarX::ControlShape shape);
|
||||||
// 设置边框颜色
|
// 设置边框颜色
|
||||||
void setTextBoxBorder(COLORREF color);
|
void setTextBoxBorder(COLORREF color);
|
||||||
|
|||||||
+134
-22
@@ -33,6 +33,16 @@ static bool SxRectsIntersect(const RECT& a, const RECT& b)
|
|||||||
a.top < b.bottom && a.bottom > b.top;
|
a.top < b.bottom && a.bottom > b.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RECT SxUnionRect(const RECT& a, const RECT& b)
|
||||||
|
{
|
||||||
|
RECT out{};
|
||||||
|
out.left = (std::min)(a.left, b.left);
|
||||||
|
out.top = (std::min)(a.top, b.top);
|
||||||
|
out.right = (std::max)(a.right, b.right);
|
||||||
|
out.bottom = (std::max)(a.bottom, b.bottom);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
static void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
|
static void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
|
||||||
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays);
|
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays);
|
||||||
|
|
||||||
@@ -50,6 +60,13 @@ bool Window::isManagedDispatchActive() const
|
|||||||
* - coverage 记录这次变化影响的范围,用于判断哪些上层 Dialog 需要补画。
|
* - coverage 记录这次变化影响的范围,用于判断哪些上层 Dialog 需要补画。
|
||||||
*/
|
*/
|
||||||
void Window::requestManagedRepaint(Control* source)
|
void Window::requestManagedRepaint(Control* source)
|
||||||
|
{
|
||||||
|
if (!source)
|
||||||
|
return;
|
||||||
|
requestManagedRepaint(source, source->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::requestManagedRepaint(Control* source, const RECT& previousCoverage)
|
||||||
{
|
{
|
||||||
if (!source)
|
if (!source)
|
||||||
return;
|
return;
|
||||||
@@ -59,29 +76,27 @@ void Window::requestManagedRepaint(Control* source)
|
|||||||
if (!root)
|
if (!root)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RECT coverage = root->getManagedRepaintCoverageRect();
|
RECT coverage = previousCoverage;
|
||||||
if (root->canCommitManagedPartialRepaint())
|
if (root->canCommitManagedPartialRepaint())
|
||||||
{
|
{
|
||||||
// 对支持局部提交的 root,coverage 不能再盯着最深处的 source;
|
// 对支持局部提交的 root,coverage 不能再盯着最深处的 source;
|
||||||
// 否则像“三层 Canvas 里的按钮变色”这种情况,只会登记成一个很小的叶子矩形,
|
// 否则像“三层 Canvas 里的按钮变色”这种情况,只会登记成一个很小的叶子矩形,
|
||||||
// 顶层 root 提交时既容易漏掉那条直接脏分支,也会低估后续 overlay 补画范围。
|
// 顶层 root 提交时既容易漏掉那条直接脏分支,也会低估后续 overlay 补画范围。
|
||||||
Control* branch = source->getManagedRepaintDirectBranch(root);
|
Control* branch = source->getManagedRepaintDirectBranch(root);
|
||||||
coverage = branch ? branch->getManagedRepaintCoverageRect() : source->getManagedRepaintCoverageRect();
|
coverage = SxUnionRect(coverage, branch ? branch->getManagedRepaintCoverageRect() : source->getManagedRepaintCoverageRect());
|
||||||
const RECT sourceCoverage = source->getManagedRepaintCoverageRect();
|
const RECT sourceCoverage = source->getManagedRepaintCoverageRect();
|
||||||
coverage.left = (std::min)(coverage.left, sourceCoverage.left);
|
coverage = SxUnionRect(coverage, sourceCoverage);
|
||||||
coverage.top = (std::min)(coverage.top, sourceCoverage.top);
|
}
|
||||||
coverage.right = (std::max)(coverage.right, sourceCoverage.right);
|
else
|
||||||
coverage.bottom = (std::max)(coverage.bottom, sourceCoverage.bottom);
|
{
|
||||||
|
coverage = SxUnionRect(coverage, root->getManagedRepaintCoverageRect());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& item : managedRepaintItems)
|
for (auto& item : managedRepaintItems)
|
||||||
{
|
{
|
||||||
if (item.root == root)
|
if (item.root == root)
|
||||||
{
|
{
|
||||||
item.coverage.left = (std::min)(item.coverage.left, coverage.left);
|
item.coverage = SxUnionRect(item.coverage, coverage);
|
||||||
item.coverage.top = (std::min)(item.coverage.top, coverage.top);
|
|
||||||
item.coverage.right = (std::max)(item.coverage.right, coverage.right);
|
|
||||||
item.coverage.bottom = (std::max)(item.coverage.bottom, coverage.bottom);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +107,43 @@ void Window::requestManagedRepaint(Control* source)
|
|||||||
managedRepaintItems.push_back(item);
|
managedRepaintItems.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::collectDirtyRootsForManagedRepaint()
|
||||||
|
{
|
||||||
|
auto isAlreadyTracked = [this](Control* root)
|
||||||
|
{
|
||||||
|
for (const auto& item : managedRepaintItems)
|
||||||
|
{
|
||||||
|
if (item.root == root)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto collectRoot = [this, &isAlreadyTracked](Control* root)
|
||||||
|
{
|
||||||
|
if (!root || !root->IsVisible())
|
||||||
|
return;
|
||||||
|
if (!root->hasManagedDirtySubtree())
|
||||||
|
return;
|
||||||
|
if (isAlreadyTracked(root))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 事件回调可以修改另一个顶层 root 的控件,例如右侧页签按钮更新左侧状态 Label。
|
||||||
|
// 这种跨 root 改脏不会经过当前事件分发链的 requestManagedRepaint(),
|
||||||
|
// 因此在 flush 前补登记为 root 级重绘,保证本轮事件尾统一提交。
|
||||||
|
ManagedRepaintItem item;
|
||||||
|
item.root = root;
|
||||||
|
item.coverage = root->getManagedRepaintCoverageRect();
|
||||||
|
managedRepaintItems.push_back(item);
|
||||||
|
managedSceneDirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& control : controls)
|
||||||
|
collectRoot(control.get());
|
||||||
|
for (auto& dialog : dialogs)
|
||||||
|
collectRoot(dialog.get());
|
||||||
|
}
|
||||||
|
|
||||||
// 清空本轮托管重绘状态;通常在 flush/全场景重绘/resize 收口后调用
|
// 清空本轮托管重绘状态;通常在 flush/全场景重绘/resize 收口后调用
|
||||||
void Window::clearManagedRepaintState()
|
void Window::clearManagedRepaintState()
|
||||||
{
|
{
|
||||||
@@ -178,6 +230,16 @@ void Window::flushManagedRepaint()
|
|||||||
root->commitManagedRepaint();
|
root->commitManagedRepaint();
|
||||||
RECT workingCoverage = initialCoverage;
|
RECT workingCoverage = initialCoverage;
|
||||||
|
|
||||||
|
if (root->canCommitManagedPartialRepaint())
|
||||||
|
{
|
||||||
|
// 当前 Canvas / TabControl 的局部提交会在内部按兄弟覆盖关系继续扩张实际写屏区域,
|
||||||
|
// 但 commitManagedRepaint() 还没有返回“本轮实际 coverage”的接口。
|
||||||
|
// 为避免 Window 仍按初始叶子区域判断,漏补上层普通控件或 Dialog,
|
||||||
|
// 这里对可局部提交的 root 使用 root 当前覆盖范围做保守兜底。
|
||||||
|
// 这只扩大“上层是否需要补画”的判断,不改变 root 本身的局部提交策略。
|
||||||
|
unionCoverage(workingCoverage, root->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
|
||||||
size_t controlStartIdx = controls.size();
|
size_t controlStartIdx = controls.size();
|
||||||
for (size_t i = 0; i < controls.size(); ++i)
|
for (size_t i = 0; i < controls.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -271,6 +333,54 @@ void Window::dispatchSyntheticMouseMoveToControls(short x, short y)
|
|||||||
(*it)->handleEvent(mm);
|
(*it)->handleEvent(mm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::syncMouseStateAfterOverlayChanged(OverlayMouseSyncMode mode)
|
||||||
|
{
|
||||||
|
short syncX = -32768;
|
||||||
|
short syncY = -32768;
|
||||||
|
|
||||||
|
if (mode == OverlayMouseSyncMode::RestoreAtCursor)
|
||||||
|
{
|
||||||
|
POINT pt{};
|
||||||
|
if (!GetCursorPos(&pt))
|
||||||
|
return;
|
||||||
|
if (!ScreenToClient(this->hWnd, &pt))
|
||||||
|
return;
|
||||||
|
syncX = static_cast<short>(pt.x);
|
||||||
|
syncY = static_cast<short>(pt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// synthetic move 仍走既有事件路径,但必须临时进入托管分发模式:
|
||||||
|
// 控件只更新 hover / tooltip / click 等瞬时状态并登记重绘,真正绘制留到事件尾统一收口。
|
||||||
|
const bool oldManagedDispatchActive = managedDispatchActive;
|
||||||
|
managedDispatchActive = true;
|
||||||
|
dispatchSyntheticMouseMoveToControls(syncX, syncY);
|
||||||
|
managedDispatchActive = oldManagedDispatchActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::sweepClosedDialogs()
|
||||||
|
{
|
||||||
|
for (auto it = dialogs.begin(); it != dialogs.end();)
|
||||||
|
{
|
||||||
|
Dialog* dialog = dynamic_cast<Dialog*>(it->get());
|
||||||
|
if (!dialog || dialog->IsVisible())
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog::Close() 只负责关闭语义和结果回调;
|
||||||
|
// 真正的快照回贴、子控件清理和对象移除必须放到事件安全点完成,
|
||||||
|
// 避免在 dialogs 正被倒序分发或绘制遍历时 erase 导致迭代器失效。
|
||||||
|
if (dialog->pendingCleanup && !dialog->isCleaning)
|
||||||
|
dialog->performDelayedCleanup();
|
||||||
|
|
||||||
|
if (!dialog->pendingCleanup && !dialog->isCleaning)
|
||||||
|
it = dialogs.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* collectManagedDialogOverlays(repaintRoot, coverage, overlays)
|
* collectManagedDialogOverlays(repaintRoot, coverage, overlays)
|
||||||
* 作用:找出在本轮提交后需要重新盖到最上层的非模态 Dialog。
|
* 作用:找出在本轮提交后需要重新盖到最上层的非模态 Dialog。
|
||||||
@@ -746,7 +856,7 @@ int Window::runEventLoop()
|
|||||||
// 会残留 hover。这里补一条落在窗口外的合成移动,只用于清理底层 hover,
|
// 会残留 hover。这里补一条落在窗口外的合成移动,只用于清理底层 hover,
|
||||||
// 不会让底层控件重新命中。
|
// 不会让底层控件重新命中。
|
||||||
if (msg.message == WM_MOUSEMOVE)
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
dispatchSyntheticMouseMoveToControls(-32768, -32768);
|
syncMouseStateAfterOverlayChanged(OverlayMouseSyncMode::ClearBehindOverlay);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,7 +879,12 @@ int Window::runEventLoop()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
current->clearTransientMouseState();
|
// 后续兄弟只清理 hover / press / tooltip 等临时鼠标状态;
|
||||||
|
// 这条路径不会再走控件自己的 handleEvent(),所以必须在顶层补登记重绘。
|
||||||
|
// 先记录旧 coverage,避免 Tooltip 隐藏后丢失原悬浮层范围。
|
||||||
|
const RECT previousCoverage = current->getManagedRepaintCoverageRect();
|
||||||
|
if (current->clearTransientMouseState())
|
||||||
|
requestManagedRepaint(current, previousCoverage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -812,17 +927,9 @@ int Window::runEventLoop()
|
|||||||
{
|
{
|
||||||
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
|
// 对话框关闭后,需要手动合成一个鼠标移动消息并分发给所有普通控件,
|
||||||
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
// 以便它们能及时更新悬停状态(hover),否则悬停状态可能保持错误状态。
|
||||||
// 先把当前鼠标位置转换为客户区坐标,并合成一次 WM_MOUSEMOVE,先分发给控件更新 hover 状态
|
// 先按当前鼠标位置刷新底层控件状态,真正绘制仍由后续全场景重绘兜底。
|
||||||
SX_LOGD("Event") << SX_T("对话框关闭,合成WM_MOUSEMOVE已下发", "Dialog closed; synthetic WM_MOUSEMOVE dispatched");
|
SX_LOGD("Event") << SX_T("对话框关闭,合成WM_MOUSEMOVE已下发", "Dialog closed; synthetic WM_MOUSEMOVE dispatched");
|
||||||
POINT pt;
|
syncMouseStateAfterOverlayChanged(OverlayMouseSyncMode::RestoreAtCursor);
|
||||||
if (GetCursorPos(&pt))
|
|
||||||
{
|
|
||||||
ScreenToClient(this->hWnd, &pt);
|
|
||||||
// 只分发给 window 层控件(因为 dialog 已经关闭或即将关闭)
|
|
||||||
managedDispatchActive = true;
|
|
||||||
dispatchSyntheticMouseMoveToControls((short)pt.x, (short)pt.y);
|
|
||||||
managedDispatchActive = false;
|
|
||||||
}
|
|
||||||
dialogClose = false; // 重置标志
|
dialogClose = false; // 重置标志
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +1052,12 @@ int Window::runEventLoop()
|
|||||||
|
|
||||||
// 普通输入事件收口:只在没有 resize / 对话框开关这种全局变化时,才提交本轮托管重绘。
|
// 普通输入事件收口:只在没有 resize / 对话框开关这种全局变化时,才提交本轮托管重绘。
|
||||||
if (!needResizeDirty && !dialogOpen && !dialogClose)
|
if (!needResizeDirty && !dialogOpen && !dialogClose)
|
||||||
|
{
|
||||||
|
collectDirtyRootsForManagedRepaint();
|
||||||
flushManagedRepaint();
|
flushManagedRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
sweepClosedDialogs();
|
||||||
|
|
||||||
// 轻微睡眠,削峰填谷(不阻塞拖拽体验)
|
// 轻微睡眠,削峰填谷(不阻塞拖拽体验)
|
||||||
Sleep(10);
|
Sleep(10);
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ public:
|
|||||||
bool isManagedDispatchActive() const;
|
bool isManagedDispatchActive() const;
|
||||||
// 记录一笔由 source 发起的托管重绘请求
|
// 记录一笔由 source 发起的托管重绘请求
|
||||||
void requestManagedRepaint(Control* source);
|
void requestManagedRepaint(Control* source);
|
||||||
|
// 记录一笔托管重绘请求,并显式并入状态清理前的旧覆盖范围
|
||||||
|
void requestManagedRepaint(Control* source, const RECT& previousCoverage);
|
||||||
// 在事件收口阶段提交本轮登记的 root 重绘
|
// 在事件收口阶段提交本轮登记的 root 重绘
|
||||||
void flushManagedRepaint();
|
void flushManagedRepaint();
|
||||||
private:
|
private:
|
||||||
@@ -124,6 +126,17 @@ private:
|
|||||||
void drawWindowBackground();
|
void drawWindowBackground();
|
||||||
// 合成 WM_MOUSEMOVE,用于同步底层 hover 状态
|
// 合成 WM_MOUSEMOVE,用于同步底层 hover 状态
|
||||||
void dispatchSyntheticMouseMoveToControls(short x, short y);
|
void dispatchSyntheticMouseMoveToControls(short x, short y);
|
||||||
|
enum class OverlayMouseSyncMode
|
||||||
|
{
|
||||||
|
ClearBehindOverlay, // overlay 吞掉鼠标移动:只清理底层 hover / tooltip,不重新命中底层控件
|
||||||
|
RestoreAtCursor // overlay 关闭:按当前鼠标位置刷新底层 hover 状态
|
||||||
|
};
|
||||||
|
// overlay 层级变化后,同步底层鼠标瞬时状态;内部仍复用既有 synthetic WM_MOUSEMOVE 路径
|
||||||
|
void syncMouseStateAfterOverlayChanged(OverlayMouseSyncMode mode);
|
||||||
|
// 在事件安全点清理已关闭的非模态 Dialog,避免 dialogs 容器长期积累无效对象
|
||||||
|
void sweepClosedDialogs();
|
||||||
|
// 事件回调可能改脏其他 root;flush 前补收集这些跨 root 脏子树
|
||||||
|
void collectDirtyRootsForManagedRepaint();
|
||||||
// 清空本轮托管重绘登记
|
// 清空本轮托管重绘登记
|
||||||
void clearManagedRepaintState();
|
void clearManagedRepaintState();
|
||||||
// 找出需要补画到最上层的对话框
|
// 找出需要补画到最上层的对话框
|
||||||
|
|||||||
+347
-79
@@ -39,12 +39,318 @@
|
|||||||
// * TabControl 页签按钮与页面层级正确;页签 tooltip 不会再被页面盖掉。
|
// * TabControl 页签按钮与页面层级正确;页签 tooltip 不会再被页面盖掉。
|
||||||
// * Table 分页按钮、页码标签、与上层浮层相交时的重绘链保持正确。
|
// * Table 分页按钮、页码标签、与上层浮层相交时的重绘链保持正确。
|
||||||
//
|
//
|
||||||
|
// 6. KEY6:已实现但主回归未覆盖的枚举/分支补充
|
||||||
|
// - 目标:补充 TabPlacement::Right、Button 圆/椭圆/禁用态、TextBox 圆角形状、
|
||||||
|
// 以及 YesNo / RetryCancel / AbortRetryIgnore 消息框类型。
|
||||||
|
// - 预期:
|
||||||
|
// * 右侧页签布局、切页、页内按钮和输入框正常。
|
||||||
|
// * 圆形/椭圆按钮命中区域符合形状,禁用按钮不触发点击回调。
|
||||||
|
// * 剩余 MessageBox 类型按钮结果链正常,非模态关闭不残留。
|
||||||
|
//
|
||||||
// 当前阶段建议主回归集:KEY1 + KEY2 + KEY5
|
// 当前阶段建议主回归集:KEY1 + KEY2 + KEY5
|
||||||
// Dialog / MessageBox 补充专项:KEY4
|
// Dialog / MessageBox 补充专项:KEY4
|
||||||
|
// 实现分支补充专项:KEY6
|
||||||
#include"StellarX.h"
|
#include"StellarX.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifndef KEY
|
#ifndef KEY
|
||||||
#define KEY 5
|
#define KEY 6
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 测试用例统一使用最新布局 API,避免继续依赖旧 setAnchor() 兼容层。
|
||||||
|
// 这些 helper 只服务 z-testDome:命名带 SxTest 前缀,避免被误认为框架公开接口。
|
||||||
|
static void SxTestSetDesignX(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
control->setHorizontalAnchors(false, false);
|
||||||
|
control->setHorizontalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setHorizontalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetDesignY(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
control->setVerticalAnchors(false, false);
|
||||||
|
control->setVerticalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setVerticalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetStretchX(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
control->setHorizontalAnchors(true, true);
|
||||||
|
control->setHorizontalSizePolicy(StellarX::AxisSizePolicy::Stretch);
|
||||||
|
control->setHorizontalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
SxTestSetDesignY(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetLeftFixed(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
control->setHorizontalAnchors(true, false);
|
||||||
|
control->setHorizontalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setHorizontalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
SxTestSetDesignY(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetRightFixed(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
control->setHorizontalAnchors(false, true);
|
||||||
|
control->setHorizontalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setHorizontalAlignPolicy(StellarX::AxisAlignPolicy::End);
|
||||||
|
SxTestSetDesignY(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetTopFixed(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
SxTestSetDesignX(control);
|
||||||
|
control->setVerticalAnchors(true, false);
|
||||||
|
control->setVerticalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setVerticalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetBottomFixed(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
SxTestSetDesignX(control);
|
||||||
|
control->setVerticalAnchors(false, true);
|
||||||
|
control->setVerticalSizePolicy(StellarX::AxisSizePolicy::FixedSize);
|
||||||
|
control->setVerticalAlignPolicy(StellarX::AxisAlignPolicy::End);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SxTestSetStretchY(Control* control)
|
||||||
|
{
|
||||||
|
if (!control) return;
|
||||||
|
SxTestSetDesignX(control);
|
||||||
|
control->setVerticalAnchors(true, true);
|
||||||
|
control->setVerticalSizePolicy(StellarX::AxisSizePolicy::Stretch);
|
||||||
|
control->setVerticalAlignPolicy(StellarX::AxisAlignPolicy::Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 6 == KEY
|
||||||
|
|
||||||
|
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(1120, 760, 1, RGB(246, 248, 251), "StellarX KEY6 分支覆盖补充");
|
||||||
|
|
||||||
|
const COLORREF panelColor = RGB(230, 238, 248);
|
||||||
|
const COLORREF shapeColor = RGB(244, 235, 218);
|
||||||
|
const COLORREF tabColor = RGB(226, 242, 235);
|
||||||
|
const COLORREF msgColor = RGB(240, 229, 245);
|
||||||
|
const COLORREF hoverColor = RGB(255, 225, 92);
|
||||||
|
const COLORREF trueColor = RGB(238, 142, 104);
|
||||||
|
const COLORREF falseColor = RGB(247, 248, 250);
|
||||||
|
|
||||||
|
auto configureButton = [&](Button* button, const std::string& tooltip)
|
||||||
|
{
|
||||||
|
button->enableTooltip(true);
|
||||||
|
button->setTooltipDelay(120);
|
||||||
|
button->setTooltipText(tooltip);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto makeButton = [&](int x, int y, int w, int h, const std::string& text)
|
||||||
|
{
|
||||||
|
auto button = std::make_unique<Button>(x, y, w, h, text, trueColor, falseColor, hoverColor);
|
||||||
|
configureButton(button.get(), text);
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto header = std::make_unique<Canvas>(20, 20, 1080, 82);
|
||||||
|
auto headerPtr = header.get();
|
||||||
|
headerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
headerPtr->setCanvasBkColor(panelColor);
|
||||||
|
SxTestSetStretchX(headerPtr);
|
||||||
|
|
||||||
|
auto title = std::make_unique<Label>(18, 10, "KEY6:已实现但主回归未覆盖的分支补充");
|
||||||
|
title->textStyle.nHeight = 26;
|
||||||
|
title->setDirty(true);
|
||||||
|
title->setTextdisap(true);
|
||||||
|
auto hint = std::make_unique<Label>(18, 48, "覆盖:Right 页签、圆/椭圆按钮命中、DISABLED 按钮、圆角 TextBox、剩余 MessageBox 类型");
|
||||||
|
hint->setTextdisap(true);
|
||||||
|
headerPtr->addControl(std::move(title));
|
||||||
|
headerPtr->addControl(std::move(hint));
|
||||||
|
|
||||||
|
auto shapePanel = std::make_unique<Canvas>(20, 120, 470, 270);
|
||||||
|
auto shapePanelPtr = shapePanel.get();
|
||||||
|
shapePanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
shapePanelPtr->setCanvasBkColor(shapeColor);
|
||||||
|
SxTestSetLeftFixed(shapePanelPtr);
|
||||||
|
|
||||||
|
auto statusLabel = std::make_unique<Label>(18, 226, "状态:等待交互");
|
||||||
|
auto statusLabelPtr = statusLabel.get();
|
||||||
|
statusLabelPtr->setTextdisap(true);
|
||||||
|
|
||||||
|
auto shapeTitle = std::make_unique<Label>(18, 14, "A:Button 形状与禁用态");
|
||||||
|
shapeTitle->setTextdisap(true);
|
||||||
|
auto shapeHint = std::make_unique<Label>(18, 40, "圆/椭圆按钮需要按形状命中;禁用按钮不应触发回调。");
|
||||||
|
shapeHint->setTextdisap(true);
|
||||||
|
|
||||||
|
auto circleButton = makeButton(30, 78, 72, 72, "圆");
|
||||||
|
circleButton->setButtonShape(StellarX::ControlShape::CIRCLE);
|
||||||
|
circleButton->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:有边框圆形按钮被点击");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto solidCircleButton = makeButton(132, 78, 72, 72, "无边圆");
|
||||||
|
solidCircleButton->setButtonShape(StellarX::ControlShape::B_CIRCLE);
|
||||||
|
solidCircleButton->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:无边框圆形按钮被点击");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto ellipseButton = makeButton(234, 84, 116, 58, "椭圆");
|
||||||
|
ellipseButton->setButtonShape(StellarX::ControlShape::ELLIPSE);
|
||||||
|
ellipseButton->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:有边框椭圆按钮被点击");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto solidEllipseButton = makeButton(30, 164, 116, 50, "无边椭圆");
|
||||||
|
solidEllipseButton->setButtonShape(StellarX::ControlShape::B_ELLIPSE);
|
||||||
|
solidEllipseButton->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:无边框椭圆按钮被点击");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto disabledButton = std::make_unique<Button>(176, 164, 116, 50, "禁用", StellarX::ButtonMode::DISABLED, StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
configureButton(disabledButton.get(), "禁用按钮:应只显示禁用态,不触发点击");
|
||||||
|
disabledButton->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:BUG,禁用按钮触发了点击");
|
||||||
|
});
|
||||||
|
|
||||||
|
shapePanelPtr->addControl(std::move(shapeTitle));
|
||||||
|
shapePanelPtr->addControl(std::move(shapeHint));
|
||||||
|
shapePanelPtr->addControl(std::move(circleButton));
|
||||||
|
shapePanelPtr->addControl(std::move(solidCircleButton));
|
||||||
|
shapePanelPtr->addControl(std::move(ellipseButton));
|
||||||
|
shapePanelPtr->addControl(std::move(solidEllipseButton));
|
||||||
|
shapePanelPtr->addControl(std::move(disabledButton));
|
||||||
|
shapePanelPtr->addControl(std::move(statusLabel));
|
||||||
|
|
||||||
|
auto tabPanel = std::make_unique<Canvas>(520, 120, 580, 270);
|
||||||
|
auto tabPanelPtr = tabPanel.get();
|
||||||
|
tabPanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
tabPanelPtr->setCanvasBkColor(tabColor);
|
||||||
|
SxTestSetStretchX(tabPanelPtr);
|
||||||
|
|
||||||
|
auto tabTitle = std::make_unique<Label>(18, 14, "B:TabPlacement::Right");
|
||||||
|
tabTitle->setTextdisap(true);
|
||||||
|
auto tabHint = std::make_unique<Label>(18, 40, "右侧页签栏应稳定,页面区宽度应扣除页签栏。");
|
||||||
|
tabHint->setTextdisap(true);
|
||||||
|
|
||||||
|
auto rightTabs = std::make_unique<TabControl>(18, 72, 542, 172);
|
||||||
|
auto rightTabsPtr = rightTabs.get();
|
||||||
|
rightTabsPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
rightTabsPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
||||||
|
rightTabsPtr->setTabPlacement(StellarX::TabPlacement::Right);
|
||||||
|
rightTabsPtr->setTabBarHeight(76);
|
||||||
|
SxTestSetStretchX(rightTabsPtr);
|
||||||
|
|
||||||
|
auto rightPage1 = std::make_unique<Canvas>(0, 0, 466, 172);
|
||||||
|
rightPage1->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
rightPage1->setCanvasBkColor(RGB(240, 248, 255));
|
||||||
|
auto rightPage1Ptr = rightPage1.get();
|
||||||
|
auto rightPage1Label = std::make_unique<Label>(16, 18, "右侧页签页 1:按钮和圆角输入框");
|
||||||
|
rightPage1Label->setTextdisap(true);
|
||||||
|
auto rightPage1Button = makeButton(18, 58, 120, 34, "页内按钮");
|
||||||
|
rightPage1Button->setOnClickListener([statusLabelPtr]()
|
||||||
|
{
|
||||||
|
statusLabelPtr->setText("状态:Right 页签页内按钮被点击");
|
||||||
|
});
|
||||||
|
auto roundInput = std::make_unique<TextBox>(160, 58, 260, 34, "圆角输入框", StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
roundInput->setTextBoxshape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
roundInput->setMaxCharLen(24);
|
||||||
|
SxTestSetStretchX(roundInput.get());
|
||||||
|
rightPage1Ptr->addControl(std::move(rightPage1Label));
|
||||||
|
rightPage1Ptr->addControl(std::move(rightPage1Button));
|
||||||
|
rightPage1Ptr->addControl(std::move(roundInput));
|
||||||
|
|
||||||
|
auto rightPage2 = std::make_unique<Canvas>(0, 0, 466, 172);
|
||||||
|
rightPage2->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
rightPage2->setCanvasBkColor(RGB(247, 244, 236));
|
||||||
|
auto rightPage2Ptr = rightPage2.get();
|
||||||
|
auto rightPage2Label = std::make_unique<Label>(16, 18, "右侧页签页 2:只读圆角 TextBox");
|
||||||
|
rightPage2Label->setTextdisap(true);
|
||||||
|
auto roundReadOnly = std::make_unique<TextBox>(18, 58, 380, 34, "只读圆角文本框", StellarX::TextBoxmode::READONLY_MODE, StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
roundReadOnly->setTextBoxshape(StellarX::ControlShape::B_ROUND_RECTANGLE);
|
||||||
|
SxTestSetStretchX(roundReadOnly.get());
|
||||||
|
rightPage2Ptr->addControl(std::move(rightPage2Label));
|
||||||
|
rightPage2Ptr->addControl(std::move(roundReadOnly));
|
||||||
|
|
||||||
|
rightTabsPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 76, 34, "页一"), std::move(rightPage1)));
|
||||||
|
rightTabsPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 76, 34, "页二"), std::move(rightPage2)));
|
||||||
|
rightTabsPtr->setActiveIndex(0);
|
||||||
|
|
||||||
|
tabPanelPtr->addControl(std::move(tabTitle));
|
||||||
|
tabPanelPtr->addControl(std::move(tabHint));
|
||||||
|
tabPanelPtr->addControl(std::move(rightTabs));
|
||||||
|
|
||||||
|
auto messagePanel = std::make_unique<Canvas>(20, 420, 1080, 250);
|
||||||
|
auto messagePanelPtr = messagePanel.get();
|
||||||
|
messagePanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
messagePanelPtr->setCanvasBkColor(msgColor);
|
||||||
|
SxTestSetStretchX(messagePanelPtr);
|
||||||
|
|
||||||
|
auto msgTitle = std::make_unique<Label>(18, 14, "C:剩余 MessageBox 类型");
|
||||||
|
msgTitle->setTextdisap(true);
|
||||||
|
auto msgHint = std::make_unique<Label>(18, 40, "分别打开 YesNo、RetryCancel、AbortRetryIgnore,观察按钮结果和关闭后 hover 恢复。");
|
||||||
|
msgHint->setTextdisap(true);
|
||||||
|
auto msgStatus = std::make_unique<Label>(18, 202, "消息框结果:等待操作");
|
||||||
|
auto msgStatusPtr = msgStatus.get();
|
||||||
|
msgStatus->setTextdisap(true);
|
||||||
|
|
||||||
|
auto yesNoButton = makeButton(30, 92, 170, 44, "模态 YesNo");
|
||||||
|
yesNoButton->setOnClickListener([&win, msgStatusPtr]()
|
||||||
|
{
|
||||||
|
auto result = StellarX::MessageBox::showModal(win, "KEY6:YesNo 分支验证", "KEY6 YesNo", StellarX::MessageBoxType::YesNo);
|
||||||
|
msgStatusPtr->setText("消息框结果:YesNo -> " + std::to_string((int)result));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto retryCancelButton = makeButton(230, 92, 190, 44, "模态 RetryCancel");
|
||||||
|
retryCancelButton->setOnClickListener([&win, msgStatusPtr]()
|
||||||
|
{
|
||||||
|
auto result = StellarX::MessageBox::showModal(win, "KEY6:RetryCancel 分支验证", "KEY6 RetryCancel", StellarX::MessageBoxType::RetryCancel);
|
||||||
|
msgStatusPtr->setText("消息框结果:RetryCancel -> " + std::to_string((int)result));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto abortRetryIgnoreButton = makeButton(450, 92, 230, 44, "非模态 AbortRetryIgnore");
|
||||||
|
abortRetryIgnoreButton->setOnClickListener([&win, msgStatusPtr]()
|
||||||
|
{
|
||||||
|
StellarX::MessageBox::showAsync(
|
||||||
|
win,
|
||||||
|
"KEY6:AbortRetryIgnore 非模态分支验证。\n关闭后检查底层按钮 hover / tooltip 是否恢复。",
|
||||||
|
"KEY6 AbortRetryIgnore",
|
||||||
|
StellarX::MessageBoxType::AbortRetryIgnore,
|
||||||
|
[msgStatusPtr](StellarX::MessageBoxResult result)
|
||||||
|
{
|
||||||
|
msgStatusPtr->setText("消息框结果:AbortRetryIgnore -> " + std::to_string((int)result));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
messagePanelPtr->addControl(std::move(msgTitle));
|
||||||
|
messagePanelPtr->addControl(std::move(msgHint));
|
||||||
|
messagePanelPtr->addControl(std::move(yesNoButton));
|
||||||
|
messagePanelPtr->addControl(std::move(retryCancelButton));
|
||||||
|
messagePanelPtr->addControl(std::move(abortRetryIgnoreButton));
|
||||||
|
messagePanelPtr->addControl(std::move(msgStatus));
|
||||||
|
|
||||||
|
win.addControl(std::move(header));
|
||||||
|
win.addControl(std::move(shapePanel));
|
||||||
|
win.addControl(std::move(tabPanel));
|
||||||
|
win.addControl(std::move(messagePanel));
|
||||||
|
|
||||||
|
win.draw();
|
||||||
|
return win.runEventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 5 == KEY
|
#if 5 == KEY
|
||||||
@@ -89,8 +395,7 @@ int main()
|
|||||||
auto headerPtr = header.get();
|
auto headerPtr = header.get();
|
||||||
headerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
headerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
headerPtr->setCanvasBkColor(headerColor);
|
headerPtr->setCanvasBkColor(headerColor);
|
||||||
headerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(headerPtr);
|
||||||
headerPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto headerTitle = std::make_unique<Label>(18, 8, "KEY5:第二阶段专项回归");
|
auto headerTitle = std::make_unique<Label>(18, 8, "KEY5:第二阶段专项回归");
|
||||||
headerTitle->textStyle.nHeight = 26;
|
headerTitle->textStyle.nHeight = 26;
|
||||||
@@ -108,8 +413,7 @@ int main()
|
|||||||
auto nestedZonePtr = nestedZone.get();
|
auto nestedZonePtr = nestedZone.get();
|
||||||
nestedZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
nestedZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
nestedZonePtr->setCanvasBkColor(nestedZoneColor);
|
nestedZonePtr->setCanvasBkColor(nestedZoneColor);
|
||||||
nestedZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(nestedZonePtr);
|
||||||
nestedZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto nestedTitle = std::make_unique<Label>(16, 16, "A 蓝:三层 Canvas 嵌套");
|
auto nestedTitle = std::make_unique<Label>(16, 16, "A 蓝:三层 Canvas 嵌套");
|
||||||
nestedTitle->setTextdisap(true);
|
nestedTitle->setTextdisap(true);
|
||||||
@@ -117,55 +421,46 @@ int main()
|
|||||||
nestedHint->setTextdisap(true);
|
nestedHint->setTextdisap(true);
|
||||||
|
|
||||||
auto nestedLeftBtn = makeTestButton(20, 74, 110, 34, "左固定");
|
auto nestedLeftBtn = makeTestButton(20, 74, 110, 34, "左固定");
|
||||||
nestedLeftBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetLeftFixed(nestedLeftBtn.get());
|
||||||
nestedLeftBtn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto nestedStretchBox = std::make_unique<TextBox>(150, 74, 350, 34, "A 区横向拉伸输入框");
|
auto nestedStretchBox = std::make_unique<TextBox>(150, 74, 350, 34, "A 区横向拉伸输入框");
|
||||||
nestedStretchBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(nestedStretchBox.get());
|
||||||
nestedStretchBox->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto nestedRightBtn = makeTestButton(520, 74, 110, 34, "右固定");
|
auto nestedRightBtn = makeTestButton(520, 74, 110, 34, "右固定");
|
||||||
nestedRightBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(nestedRightBtn.get());
|
||||||
nestedRightBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto level1 = std::make_unique<Canvas>(18, 126, 614, 134);
|
auto level1 = std::make_unique<Canvas>(18, 126, 614, 134);
|
||||||
auto level1Ptr = level1.get();
|
auto level1Ptr = level1.get();
|
||||||
level1Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
level1Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
level1Ptr->setCanvasBkColor(level1Color);
|
level1Ptr->setCanvasBkColor(level1Color);
|
||||||
level1Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(level1Ptr);
|
||||||
level1Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto level1Title = std::make_unique<Label>(14, 10, "A-1 青:第一层");
|
auto level1Title = std::make_unique<Label>(14, 10, "A-1 青:第一层");
|
||||||
level1Title->setTextdisap(true);
|
level1Title->setTextdisap(true);
|
||||||
auto level1Btn = makeTestButton(18, 42, 110, 30, "内层左");
|
auto level1Btn = makeTestButton(18, 42, 110, 30, "内层左");
|
||||||
level1Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetLeftFixed(level1Btn.get());
|
||||||
level1Btn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto level2 = std::make_unique<Canvas>(142, 36, 454, 84);
|
auto level2 = std::make_unique<Canvas>(142, 36, 454, 84);
|
||||||
auto level2Ptr = level2.get();
|
auto level2Ptr = level2.get();
|
||||||
level2Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
level2Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
level2Ptr->setCanvasBkColor(level2Color);
|
level2Ptr->setCanvasBkColor(level2Color);
|
||||||
level2Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(level2Ptr);
|
||||||
level2Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto level2Title = std::make_unique<Label>(12, 8, "A-2 绿:第二层");
|
auto level2Title = std::make_unique<Label>(12, 8, "A-2 绿:第二层");
|
||||||
level2Title->setTextdisap(true);
|
level2Title->setTextdisap(true);
|
||||||
auto level2Btn = makeTestButton(332, 8, 108, 28, "右固定");
|
auto level2Btn = makeTestButton(332, 8, 108, 28, "右固定");
|
||||||
level2Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(level2Btn.get());
|
||||||
level2Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto level3 = std::make_unique<Canvas>(16, 42, 422, 28);
|
auto level3 = std::make_unique<Canvas>(16, 42, 422, 28);
|
||||||
auto level3Ptr = level3.get();
|
auto level3Ptr = level3.get();
|
||||||
level3Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
level3Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
level3Ptr->setCanvasBkColor(level3Color);
|
level3Ptr->setCanvasBkColor(level3Color);
|
||||||
level3Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(level3Ptr);
|
||||||
level3Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto level3Label = std::make_unique<Label>(10, 6, "A-3 橙:第三层");
|
auto level3Label = std::make_unique<Label>(10, 6, "A-3 橙:第三层");
|
||||||
level3Label->setTextdisap(true);
|
level3Label->setTextdisap(true);
|
||||||
auto level3Btn = makeTestButton(312, 2, 98, 24, "右按钮");
|
auto level3Btn = makeTestButton(312, 2, 98, 24, "右按钮");
|
||||||
level3Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(level3Btn.get());
|
||||||
level3Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(level3Btn.get(), "第三层 Canvas 里的右固定按钮");
|
configureTooltip(level3Btn.get(), "第三层 Canvas 里的右固定按钮");
|
||||||
|
|
||||||
level3Ptr->addControl(std::move(level3Label));
|
level3Ptr->addControl(std::move(level3Label));
|
||||||
@@ -188,8 +483,7 @@ int main()
|
|||||||
auto tabZonePtr = tabZone.get();
|
auto tabZonePtr = tabZone.get();
|
||||||
tabZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
tabZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
tabZonePtr->setCanvasBkColor(tabZoneColor);
|
tabZonePtr->setCanvasBkColor(tabZoneColor);
|
||||||
tabZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(tabZonePtr);
|
||||||
tabZonePtr->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto tabTitle = std::make_unique<Label>(16, 16, "B 青:TabControl 外层 resize");
|
auto tabTitle = std::make_unique<Label>(16, 16, "B 青:TabControl 外层 resize");
|
||||||
tabTitle->setTextdisap(true);
|
tabTitle->setTextdisap(true);
|
||||||
@@ -202,8 +496,7 @@ int main()
|
|||||||
tabControlPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
tabControlPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
||||||
tabControlPtr->setTabPlacement(StellarX::TabPlacement::Top);
|
tabControlPtr->setTabPlacement(StellarX::TabPlacement::Top);
|
||||||
tabControlPtr->setTabBarHeight(30);
|
tabControlPtr->setTabBarHeight(30);
|
||||||
tabControlPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(tabControlPtr);
|
||||||
tabControlPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto tabPage1 = std::make_unique<Canvas>(0, 0, 634, 170);
|
auto tabPage1 = std::make_unique<Canvas>(0, 0, 634, 170);
|
||||||
auto tabPage1Ptr = tabPage1.get();
|
auto tabPage1Ptr = tabPage1.get();
|
||||||
@@ -218,25 +511,21 @@ int main()
|
|||||||
auto page1Label = std::make_unique<Label>(14, 12, "页 1:页内 TextBox 继续由 Canvas 管。");
|
auto page1Label = std::make_unique<Label>(14, 12, "页 1:页内 TextBox 继续由 Canvas 管。");
|
||||||
page1Label->setTextdisap(true);
|
page1Label->setTextdisap(true);
|
||||||
auto page1Box = std::make_unique<TextBox>(18, 44, 468, 34, "页 1 左右拉伸输入框");
|
auto page1Box = std::make_unique<TextBox>(18, 44, 468, 34, "页 1 左右拉伸输入框");
|
||||||
page1Box->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(page1Box.get());
|
||||||
page1Box->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
auto page1Btn = makeTestButton(504, 44, 96, 34, "右固定");
|
auto page1Btn = makeTestButton(504, 44, 96, 34, "右固定");
|
||||||
page1Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(page1Btn.get());
|
||||||
page1Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(page1Btn.get(), "页 1 右固定按钮");
|
configureTooltip(page1Btn.get(), "页 1 右固定按钮");
|
||||||
|
|
||||||
auto page1InnerCanvas = std::make_unique<Canvas>(18, 92, 582, 56);
|
auto page1InnerCanvas = std::make_unique<Canvas>(18, 92, 582, 56);
|
||||||
auto page1InnerCanvasPtr = page1InnerCanvas.get();
|
auto page1InnerCanvasPtr = page1InnerCanvas.get();
|
||||||
page1InnerCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
page1InnerCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
page1InnerCanvasPtr->setCanvasBkColor(RGB(223, 236, 248));
|
page1InnerCanvasPtr->setCanvasBkColor(RGB(223, 236, 248));
|
||||||
page1InnerCanvasPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(page1InnerCanvasPtr);
|
||||||
page1InnerCanvasPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto page1InnerLabel = std::make_unique<Label>(10, 8, "页 1 内层 Canvas");
|
auto page1InnerLabel = std::make_unique<Label>(10, 8, "页 1 内层 Canvas");
|
||||||
page1InnerLabel->setTextdisap(true);
|
page1InnerLabel->setTextdisap(true);
|
||||||
auto page1InnerBtn = makeTestButton(462, 14, 108, 28, "右按钮");
|
auto page1InnerBtn = makeTestButton(462, 14, 108, 28, "右按钮");
|
||||||
page1InnerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(page1InnerBtn.get());
|
||||||
page1InnerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(page1InnerBtn.get(), "页 1 内层 Canvas 按钮");
|
configureTooltip(page1InnerBtn.get(), "页 1 内层 Canvas 按钮");
|
||||||
|
|
||||||
page1InnerCanvasPtr->addControl(std::move(page1InnerLabel));
|
page1InnerCanvasPtr->addControl(std::move(page1InnerLabel));
|
||||||
@@ -259,8 +548,7 @@ int main()
|
|||||||
auto page2InnerLabel = std::make_unique<Label>(10, 8, "页 2 内层 Canvas");
|
auto page2InnerLabel = std::make_unique<Label>(10, 8, "页 2 内层 Canvas");
|
||||||
page2InnerLabel->setTextdisap(true);
|
page2InnerLabel->setTextdisap(true);
|
||||||
auto page2InnerBtn = makeTestButton(286, 42, 126, 28, "右侧 Tooltip");
|
auto page2InnerBtn = makeTestButton(286, 42, 126, 28, "右侧 Tooltip");
|
||||||
page2InnerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(page2InnerBtn.get());
|
||||||
page2InnerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(page2InnerBtn.get(), "页 2 内层按钮 Tooltip");
|
configureTooltip(page2InnerBtn.get(), "页 2 内层按钮 Tooltip");
|
||||||
|
|
||||||
page2InnerCanvasPtr->addControl(std::move(page2InnerLabel));
|
page2InnerCanvasPtr->addControl(std::move(page2InnerLabel));
|
||||||
@@ -323,8 +611,7 @@ int main()
|
|||||||
});
|
});
|
||||||
|
|
||||||
auto innerHoverBtn = makeTestButton(506, 120, 118, 34, "容器内按钮");
|
auto innerHoverBtn = makeTestButton(506, 120, 118, 34, "容器内按钮");
|
||||||
innerHoverBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(innerHoverBtn.get());
|
||||||
innerHoverBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(innerHoverBtn.get(), "Canvas 内按钮 Tooltip");
|
configureTooltip(innerHoverBtn.get(), "Canvas 内按钮 Tooltip");
|
||||||
|
|
||||||
behaviorZonePtr->addControl(std::move(behaviorTitle));
|
behaviorZonePtr->addControl(std::move(behaviorTitle));
|
||||||
@@ -338,8 +625,7 @@ int main()
|
|||||||
auto overlayZonePtr = overlayZone.get();
|
auto overlayZonePtr = overlayZone.get();
|
||||||
overlayZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
overlayZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
overlayZonePtr->setCanvasBkColor(overlayZoneColor);
|
overlayZonePtr->setCanvasBkColor(overlayZoneColor);
|
||||||
overlayZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(overlayZonePtr);
|
||||||
overlayZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto overlayTitle = std::make_unique<Label>(16, 16, "D 橙:Top+Bottom + 同父 overlay");
|
auto overlayTitle = std::make_unique<Label>(16, 16, "D 橙:Top+Bottom + 同父 overlay");
|
||||||
overlayTitle->setTextdisap(true);
|
overlayTitle->setTextdisap(true);
|
||||||
@@ -348,17 +634,14 @@ int main()
|
|||||||
auto lowerLayerPtr = lowerLayer.get();
|
auto lowerLayerPtr = lowerLayer.get();
|
||||||
lowerLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
lowerLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
lowerLayerPtr->setCanvasBkColor(RGB(255, 245, 231));
|
lowerLayerPtr->setCanvasBkColor(RGB(255, 245, 231));
|
||||||
lowerLayerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchY(lowerLayerPtr);
|
||||||
lowerLayerPtr->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::Bottom);
|
|
||||||
|
|
||||||
auto lowerTitle = std::make_unique<Label>(12, 8, "D-1 下层区");
|
auto lowerTitle = std::make_unique<Label>(12, 8, "D-1 下层区");
|
||||||
lowerTitle->setTextdisap(true);
|
lowerTitle->setTextdisap(true);
|
||||||
auto lowerBox = std::make_unique<TextBox>(12, 34, 222, 30, "下层区输入框");
|
auto lowerBox = std::make_unique<TextBox>(12, 34, 222, 30, "下层区输入框");
|
||||||
lowerBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(lowerBox.get());
|
||||||
lowerBox->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
auto lowerBtn = makeTestButton(248, 34, 92, 30, "下层按钮");
|
auto lowerBtn = makeTestButton(248, 34, 92, 30, "下层按钮");
|
||||||
lowerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(lowerBtn.get());
|
||||||
lowerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(lowerBtn.get(), "下层 Canvas 按钮");
|
configureTooltip(lowerBtn.get(), "下层 Canvas 按钮");
|
||||||
|
|
||||||
lowerLayerPtr->addControl(std::move(lowerTitle));
|
lowerLayerPtr->addControl(std::move(lowerTitle));
|
||||||
@@ -369,22 +652,19 @@ int main()
|
|||||||
auto upperLayerPtr = upperLayer.get();
|
auto upperLayerPtr = upperLayer.get();
|
||||||
upperLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
upperLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
upperLayerPtr->setCanvasBkColor(RGB(255, 224, 206));
|
upperLayerPtr->setCanvasBkColor(RGB(255, 224, 206));
|
||||||
upperLayerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(upperLayerPtr);
|
||||||
upperLayerPtr->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto upperTitle = std::make_unique<Label>(12, 8, "D-2 上层区");
|
auto upperTitle = std::make_unique<Label>(12, 8, "D-2 上层区");
|
||||||
upperTitle->setTextdisap(true);
|
upperTitle->setTextdisap(true);
|
||||||
auto upperBtn = makeTestButton(244, 38, 122, 28, "上层 Tooltip");
|
auto upperBtn = makeTestButton(244, 38, 122, 28, "上层 Tooltip");
|
||||||
upperBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(upperBtn.get());
|
||||||
upperBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(upperBtn.get(), "上层 Canvas 按钮");
|
configureTooltip(upperBtn.get(), "上层 Canvas 按钮");
|
||||||
|
|
||||||
upperLayerPtr->addControl(std::move(upperTitle));
|
upperLayerPtr->addControl(std::move(upperTitle));
|
||||||
upperLayerPtr->addControl(std::move(upperBtn));
|
upperLayerPtr->addControl(std::move(upperBtn));
|
||||||
|
|
||||||
auto overlayBottomBtn = makeTestButton(526, 148, 118, 30, "底边固定");
|
auto overlayBottomBtn = makeTestButton(526, 148, 118, 30, "底边固定");
|
||||||
overlayBottomBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetBottomFixed(overlayBottomBtn.get());
|
||||||
overlayBottomBtn->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
overlayZonePtr->addControl(std::move(overlayTitle));
|
overlayZonePtr->addControl(std::move(overlayTitle));
|
||||||
overlayZonePtr->addControl(std::move(lowerLayer));
|
overlayZonePtr->addControl(std::move(lowerLayer));
|
||||||
@@ -395,8 +675,7 @@ int main()
|
|||||||
auto tableZonePtr = tableZone.get();
|
auto tableZonePtr = tableZone.get();
|
||||||
tableZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
tableZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
tableZonePtr->setCanvasBkColor(tableZoneColor);
|
tableZonePtr->setCanvasBkColor(tableZoneColor);
|
||||||
tableZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(tableZonePtr);
|
||||||
tableZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto tableTitle = std::make_unique<Label>(16, 12, "E 紫:Table 仅 X Stretch");
|
auto tableTitle = std::make_unique<Label>(16, 12, "E 紫:Table 仅 X Stretch");
|
||||||
tableTitle->setTextdisap(true);
|
tableTitle->setTextdisap(true);
|
||||||
@@ -415,8 +694,7 @@ int main()
|
|||||||
tablePtr->setRowsPerPage(2);
|
tablePtr->setRowsPerPage(2);
|
||||||
tablePtr->setTableBorderWidth(1);
|
tablePtr->setTableBorderWidth(1);
|
||||||
tablePtr->setTableBorder(RGB(86, 88, 132));
|
tablePtr->setTableBorder(RGB(86, 88, 132));
|
||||||
tablePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(tablePtr);
|
||||||
tablePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
tableZonePtr->addControl(std::move(tableTitle));
|
tableZonePtr->addControl(std::move(tableTitle));
|
||||||
tableZonePtr->addControl(std::move(tableHint));
|
tableZonePtr->addControl(std::move(tableHint));
|
||||||
@@ -429,16 +707,14 @@ int main()
|
|||||||
auto floatingZonePtr = floatingZone.get();
|
auto floatingZonePtr = floatingZone.get();
|
||||||
floatingZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
floatingZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
floatingZonePtr->setCanvasBkColor(floatingColor);
|
floatingZonePtr->setCanvasBkColor(floatingColor);
|
||||||
floatingZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetBottomFixed(floatingZonePtr);
|
||||||
floatingZonePtr->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto floatingTitle = std::make_unique<Label>(16, 12, "F 粉:Bottom only 浮层");
|
auto floatingTitle = std::make_unique<Label>(16, 12, "F 粉:Bottom only 浮层");
|
||||||
floatingTitle->setTextdisap(true);
|
floatingTitle->setTextdisap(true);
|
||||||
auto floatingHint = std::make_unique<Label>(16, 36, "看 Table 下层重绘后的补画。");
|
auto floatingHint = std::make_unique<Label>(16, 36, "看 Table 下层重绘后的补画。");
|
||||||
floatingHint->setTextdisap(true);
|
floatingHint->setTextdisap(true);
|
||||||
auto floatingBtn = makeTestButton(322, 88, 118, 30, "右固定");
|
auto floatingBtn = makeTestButton(322, 88, 118, 30, "右固定");
|
||||||
floatingBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetRightFixed(floatingBtn.get());
|
||||||
floatingBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
configureTooltip(floatingBtn.get(), "粉色浮层里的按钮");
|
configureTooltip(floatingBtn.get(), "粉色浮层里的按钮");
|
||||||
|
|
||||||
floatingZonePtr->addControl(std::move(floatingTitle));
|
floatingZonePtr->addControl(std::move(floatingTitle));
|
||||||
@@ -582,8 +858,7 @@ int main()
|
|||||||
auto infoPanelPtr = infoPanel.get();
|
auto infoPanelPtr = infoPanel.get();
|
||||||
infoPanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
infoPanelPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
infoPanelPtr->setCanvasBkColor(RGB(225, 234, 244));
|
infoPanelPtr->setCanvasBkColor(RGB(225, 234, 244));
|
||||||
infoPanelPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(infoPanelPtr);
|
||||||
infoPanelPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto infoTitle = std::make_unique<Label>(20, 10, "综合回归测试 4:对话框遮挡、快速 Hover、分页按钮、模态 Resize");
|
auto infoTitle = std::make_unique<Label>(20, 10, "综合回归测试 4:对话框遮挡、快速 Hover、分页按钮、模态 Resize");
|
||||||
infoTitle->textStyle.nHeight = 26;
|
infoTitle->textStyle.nHeight = 26;
|
||||||
@@ -613,8 +888,7 @@ int main()
|
|||||||
auto hoverCanvasPtr = hoverCanvas.get();
|
auto hoverCanvasPtr = hoverCanvas.get();
|
||||||
hoverCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
hoverCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
hoverCanvasPtr->setCanvasBkColor(RGB(230, 238, 248));
|
hoverCanvasPtr->setCanvasBkColor(RGB(230, 238, 248));
|
||||||
hoverCanvasPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(hoverCanvasPtr);
|
||||||
hoverCanvasPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto hoverTitle = std::make_unique<Label>(20, 8, "区域 A:打开非模态对话框后,请在下面 8 个按钮之间快速来回划动鼠标。");
|
auto hoverTitle = std::make_unique<Label>(20, 8, "区域 A:打开非模态对话框后,请在下面 8 个按钮之间快速来回划动鼠标。");
|
||||||
hoverTitle->setTextdisap(true);
|
hoverTitle->setTextdisap(true);
|
||||||
@@ -635,8 +909,7 @@ int main()
|
|||||||
tabControlPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
tabControlPtr->setCanvasfillMode(StellarX::FillMode::Null);
|
||||||
tabControlPtr->setTabPlacement(StellarX::TabPlacement::Top);
|
tabControlPtr->setTabPlacement(StellarX::TabPlacement::Top);
|
||||||
tabControlPtr->setTabBarHeight(28);
|
tabControlPtr->setTabBarHeight(28);
|
||||||
tabControlPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(tabControlPtr);
|
||||||
tabControlPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto page1 = std::make_unique<Canvas>(0, 0, 1040, 152);
|
auto page1 = std::make_unique<Canvas>(0, 0, 1040, 152);
|
||||||
page1->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
page1->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
@@ -694,8 +967,7 @@ int main()
|
|||||||
tablePtr->setTableBorderWidth(1);
|
tablePtr->setTableBorderWidth(1);
|
||||||
tablePtr->setTableBorder(RGB(60, 90, 120));
|
tablePtr->setTableBorder(RGB(60, 90, 120));
|
||||||
tablePtr->setTableFillMode(StellarX::FillMode::Null);
|
tablePtr->setTableFillMode(StellarX::FillMode::Null);
|
||||||
tablePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchX(tablePtr);
|
||||||
tablePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
const std::string asyncText =
|
const std::string asyncText =
|
||||||
"KEY4 非模态回归测试正在进行。\n"
|
"KEY4 非模态回归测试正在进行。\n"
|
||||||
@@ -1289,15 +1561,14 @@ int main()
|
|||||||
for (auto& log : logIn_label)
|
for (auto& log : logIn_label)
|
||||||
{
|
{
|
||||||
log->setTextdisap(true);
|
log->setTextdisap(true);
|
||||||
log->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
log->textStyle.lpszFace = "华文行楷";
|
log->textStyle.lpszFace = "华文行楷";
|
||||||
}
|
}
|
||||||
logIn_label[0]->textStyle.nHeight = 100;
|
logIn_label[0]->textStyle.nHeight = 100;
|
||||||
logIn_label[0]->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::NoAnchor);
|
SxTestSetTopFixed(logIn_label[0].get());
|
||||||
logIn_label[1]->textStyle.nHeight = 50;
|
logIn_label[1]->textStyle.nHeight = 50;
|
||||||
logIn_label[1]->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
SxTestSetBottomFixed(logIn_label[1].get());
|
||||||
logIn_label[2]->textStyle.nHeight = 50;
|
logIn_label[2]->textStyle.nHeight = 50;
|
||||||
logIn_label[2]->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
SxTestSetBottomFixed(logIn_label[2].get());
|
||||||
|
|
||||||
//输入框
|
//输入框
|
||||||
std::unique_ptr<TextBox> logIn_textBox[2];
|
std::unique_ptr<TextBox> logIn_textBox[2];
|
||||||
@@ -1309,8 +1580,7 @@ int main()
|
|||||||
logIn_textBox_ptr[1] = logIn_textBox[1].get();
|
logIn_textBox_ptr[1] = logIn_textBox[1].get();
|
||||||
for (auto& tb : logIn_textBox)
|
for (auto& tb : logIn_textBox)
|
||||||
{
|
{
|
||||||
tb->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetBottomFixed(tb.get());
|
||||||
tb->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
tb->setMaxCharLen(15);
|
tb->setMaxCharLen(15);
|
||||||
}
|
}
|
||||||
//按钮
|
//按钮
|
||||||
@@ -1322,16 +1592,14 @@ int main()
|
|||||||
logIn_Button_ptr[1] = logIn_Button[1].get();
|
logIn_Button_ptr[1] = logIn_Button[1].get();
|
||||||
for (auto& b : logIn_Button)
|
for (auto& b : logIn_Button)
|
||||||
{
|
{
|
||||||
b->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetBottomFixed(b.get());
|
||||||
b->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
}
|
}
|
||||||
//log画布
|
//log画布
|
||||||
auto log_Canvas = std::make_unique<Canvas>(0, 0, 1300, 800);
|
auto log_Canvas = std::make_unique<Canvas>(0, 0, 1300, 800);
|
||||||
Canvas* log_Canvas_ptr = log_Canvas.get();
|
Canvas* log_Canvas_ptr = log_Canvas.get();
|
||||||
log_Canvas_ptr->setCanvasfillMode(StellarX::FillMode::Null);
|
log_Canvas_ptr->setCanvasfillMode(StellarX::FillMode::Null);
|
||||||
log_Canvas_ptr->setShape(StellarX::ControlShape::B_RECTANGLE);
|
log_Canvas_ptr->setShape(StellarX::ControlShape::B_RECTANGLE);
|
||||||
log_Canvas_ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
SxTestSetStretchY(log_Canvas_ptr);
|
||||||
log_Canvas_ptr->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::Top);
|
|
||||||
|
|
||||||
//将log界面控价加入logCanvas统一管理
|
//将log界面控价加入logCanvas统一管理
|
||||||
for (auto& b : logIn_Button)
|
for (auto& b : logIn_Button)
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# BUG-20260511-0009
|
||||||
|
|
||||||
|
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- ID: BUG-20260511-0009
|
||||||
|
- 标题: TabControl::indexOf 未命中时返回最后索引
|
||||||
|
- 状态:已修复
|
||||||
|
- 严重性:S3
|
||||||
|
- 优先级:P2
|
||||||
|
- 模块: TabControl
|
||||||
|
- 版本 / 分支: 当前工作区
|
||||||
|
- 环境: Windows / EasyX / z-testDome 回归场景
|
||||||
|
- 发现人: Codex 巡检
|
||||||
|
- 关联 Fix ID: Fix-BUG-20260511-0009
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
- 现象: `TabControl::indexOf(const std::string& tabText) const` 在未找到目标页签时返回循环后的最后索引。
|
||||||
|
- 影响范围: 依赖 `indexOf()` 判断页签是否存在的调用方可能把“不存在”误判为最后一个页签。
|
||||||
|
- 期望结果: 找到匹配页签时返回对应索引;未找到时返回 `-1`。
|
||||||
|
- 实际结果: 未找到时返回最后一个页签的索引。
|
||||||
|
|
||||||
|
## 复现信息
|
||||||
|
|
||||||
|
- 前置条件:[可选]
|
||||||
|
- 复现步骤:
|
||||||
|
|
||||||
|
1. 构造包含至少一个页签的 `TabControl`。
|
||||||
|
2. 调用 `indexOf()` 查询不存在的页签文本。
|
||||||
|
3. 观察返回值。
|
||||||
|
|
||||||
|
- 复现概率:必现
|
||||||
|
- 最小复现 Demo:[可选]
|
||||||
|
- 证据:源码巡检可证
|
||||||
|
|
||||||
|
## 初步分析
|
||||||
|
|
||||||
|
- 疑似位置: `TabControl.cpp::indexOf`
|
||||||
|
- 触发条件: 查询文本不存在。
|
||||||
|
- 相关线索: 函数名和预期语义要求“不存在返回 -1”,但旧实现返回 `idx`。
|
||||||
|
- 最近相关改动:[可选]
|
||||||
|
|
||||||
|
## 跟踪信息
|
||||||
|
|
||||||
|
- 首次发现时间: 2026-05-11
|
||||||
|
- 最后更新时间: 2026-05-11
|
||||||
|
- 修复版本: 当前工作区
|
||||||
|
- 验证版本: KEY1 ~ KEY6 编译验证通过
|
||||||
|
- 备注:[可选]
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# BUG-20260511-0010
|
||||||
|
|
||||||
|
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- ID: BUG-20260511-0010
|
||||||
|
- 标题: 局部提交内部 coverage 扩张未反馈导致 Dialog 漏补
|
||||||
|
- 状态:已修复
|
||||||
|
- 严重性:S2
|
||||||
|
- 优先级:P1
|
||||||
|
- 模块: Window / Canvas / Dialog / 托管重绘
|
||||||
|
- 版本 / 分支: 当前工作区
|
||||||
|
- 环境: Windows / EasyX / KEY6
|
||||||
|
- 发现人: 用户
|
||||||
|
- 关联 Fix ID: Fix-BUG-20260511-0010
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
- 现象: KEY6 中非模态 Dialog 打开后,触发旁边按钮 Tooltip,某些位置会导致下层按钮重绘覆盖 Dialog。
|
||||||
|
- 影响范围: 可局部提交的 root 在内部补画兄弟控件后,如果实际写屏范围扩大,Window 可能仍按初始 coverage 判断上层 overlay。
|
||||||
|
- 期望结果: 下层 root 内部扩张后的实际写屏区域能触发上层 Dialog 补画。
|
||||||
|
- 实际结果: Window 使用初始 coverage,导致 Dialog overlay 补画漏判。
|
||||||
|
|
||||||
|
## 复现信息
|
||||||
|
|
||||||
|
- 前置条件:[可选] 打开 KEY6,触发非模态 `AbortRetryIgnore` 对话框。
|
||||||
|
- 复现步骤:
|
||||||
|
|
||||||
|
1. 最大化窗口。
|
||||||
|
2. 打开 KEY6 非模态 `AbortRetryIgnore` 对话框。
|
||||||
|
3. 在 `RetryCancel` 按钮特定区域触发 Tooltip。
|
||||||
|
4. 观察非模态 Dialog 是否被下层按钮局部重绘覆盖。
|
||||||
|
|
||||||
|
- 复现概率:高概率
|
||||||
|
- 最小复现 Demo:KEY6
|
||||||
|
- 证据:临时 `OverlayTemp` 日志显示 Canvas 内部 coverage 从 `(250,512,490,583)` 扩张到 `(250,512,700,583)`,但 Window 仍用旧 working coverage 判断 Dialog,相交结果为 `hit=0`。
|
||||||
|
|
||||||
|
## 初步分析
|
||||||
|
|
||||||
|
- 疑似位置: `Window::flushManagedRepaint()` 与 `Canvas::requestRepaint(this)` 的 coverage 交接。
|
||||||
|
- 触发条件: root 支持局部提交,内部补画后序兄弟导致实际 coverage 扩张。
|
||||||
|
- 相关线索: Dialog 区域与内部最终 coverage 相交,但与 Window 初始 working coverage 不相交。
|
||||||
|
- 最近相关改动:[可选] 托管局部重绘与 overlay 补画收口。
|
||||||
|
|
||||||
|
## 跟踪信息
|
||||||
|
|
||||||
|
- 首次发现时间: 2026-05-11
|
||||||
|
- 最后更新时间: 2026-05-11
|
||||||
|
- 修复版本: 当前工作区
|
||||||
|
- 验证版本: KEY6 编译通过,待用户手测
|
||||||
|
- 备注:[可选] 精确 actual coverage 回传作为下版本优化项。
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# BUG-20260511-0011
|
||||||
|
|
||||||
|
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- ID: BUG-20260511-0011
|
||||||
|
- 标题: 跨 root 回调改脏未同轮提交导致 Label 延迟刷新
|
||||||
|
- 状态:已修复
|
||||||
|
- 严重性:S3
|
||||||
|
- 优先级:P1
|
||||||
|
- 模块: Window / 托管重绘 / Label / TabControl
|
||||||
|
- 版本 / 分支: 当前工作区
|
||||||
|
- 环境: Windows / EasyX / KEY6
|
||||||
|
- 发现人: 用户
|
||||||
|
- 关联 Fix ID: Fix-BUG-20260511-0011
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
- 现象: KEY6 中点击 Right 页签内按钮后,A 区状态 Label 不立即刷新,需要下一次左键消息才显示。
|
||||||
|
- 影响范围: 事件回调修改了另一个顶层 root 下的控件状态时,目标 root 可能不会进入本轮托管重绘队列。
|
||||||
|
- 期望结果: 回调中改脏的跨 root 控件能在同一轮事件尾刷新。
|
||||||
|
- 实际结果: 只有当前事件分发链所在 root 被提交,其他 root 的 dirty 子树滞留到下一次事件。
|
||||||
|
|
||||||
|
## 复现信息
|
||||||
|
|
||||||
|
- 前置条件:[可选] 打开 KEY6,激活右侧页签第一页。
|
||||||
|
- 复现步骤:
|
||||||
|
|
||||||
|
1. 点击 Right 页签中的“页内按钮”。
|
||||||
|
2. 观察 A 区状态 Label。
|
||||||
|
3. 再点击空白处或其他控件。
|
||||||
|
|
||||||
|
- 复现概率:必现
|
||||||
|
- 最小复现 Demo:KEY6
|
||||||
|
- 证据:`Label::setText()` 只标脏目标 Label,不主动把其所属 `shapePanel` root 登记到 Window。
|
||||||
|
|
||||||
|
## 初步分析
|
||||||
|
|
||||||
|
- 疑似位置: `Window` 事件尾托管重绘收口。
|
||||||
|
- 触发条件: 当前事件 root 与被回调修改的目标 root 不同。
|
||||||
|
- 相关线索: A 区 Label 在下一次左键消息后才刷新,说明 dirty 状态存在但未同轮提交。
|
||||||
|
- 最近相关改动:[可选] 托管重绘统一收口。
|
||||||
|
|
||||||
|
## 跟踪信息
|
||||||
|
|
||||||
|
- 首次发现时间: 2026-05-11
|
||||||
|
- 最后更新时间: 2026-05-11
|
||||||
|
- 修复版本: 当前工作区
|
||||||
|
- 验证版本: KEY6 编译通过,待用户手测
|
||||||
|
- 备注:[可选]
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# BUG-20260511-0012
|
||||||
|
|
||||||
|
> 适用场景:记录问题本身,不展开完整修复方案。修复内容写入对应的 Fix 文档。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- ID: BUG-20260511-0012
|
||||||
|
- 标题: Tooltip 临时浮层污染或擦除兄弟控件快照
|
||||||
|
- 状态:已修复
|
||||||
|
- 严重性:S2
|
||||||
|
- 优先级:P1
|
||||||
|
- 模块: Tooltip / Canvas / TabControl / 托管重绘 / 背景快照
|
||||||
|
- 版本 / 分支: 当前工作区
|
||||||
|
- 环境: Windows / EasyX / KEY6
|
||||||
|
- 发现人: 用户
|
||||||
|
- 关联 Fix ID: Fix-BUG-20260511-0012
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
- 现象: KEY6 A 区禁用按钮触发 Tooltip 后,如果 Tooltip 位于状态 Label 下方,状态 Label 可能捕获 Tooltip 到背景快照,或用透明 Label 的旧快照擦掉 Tooltip 一部分。
|
||||||
|
- 影响范围: 同一容器内,绘制顺序在 Tooltip 触发控件之后、且与 Tooltip coverage 相交的兄弟控件。
|
||||||
|
- 期望结果: Tooltip 作为临时浮层显示,不应污染兄弟控件背景快照,也不应被透明 Label 的旧快照擦除。
|
||||||
|
- 实际结果: 旧逻辑把 Tooltip coverage 与持久绘制 coverage 混用,导致兄弟控件作废快照并重抓,或保留快照重画时擦掉 Tooltip。
|
||||||
|
|
||||||
|
## 复现信息
|
||||||
|
|
||||||
|
- 前置条件:[可选] 打开 KEY6。
|
||||||
|
- 复现步骤:
|
||||||
|
|
||||||
|
1. 将鼠标移到 A 区禁用按钮上触发 Tooltip。
|
||||||
|
2. 让 Tooltip 显示区域位于状态 Label 下方。
|
||||||
|
3. 观察状态 Label 区域是否覆盖 / 擦除 Tooltip。
|
||||||
|
4. 触发下一次重绘后观察是否有 Tooltip 残留。
|
||||||
|
|
||||||
|
- 复现概率:高概率
|
||||||
|
- 最小复现 Demo:KEY6 A 区
|
||||||
|
- 证据:`Label::draw()` 即使透明也会先 `restBackground()`;如果被临时浮层触发补画,会回贴旧快照。
|
||||||
|
|
||||||
|
## 初步分析
|
||||||
|
|
||||||
|
- 疑似位置: `Canvas::requestRepaint(this)` / `TabControl::requestRepaint(this)` 的局部 overlay 补画策略。
|
||||||
|
- 触发条件: 下层 Button Tooltip 的完整 coverage 与后序兄弟控件相交。
|
||||||
|
- 相关线索:
|
||||||
|
- `Button::getManagedRepaintCoverageRect()` 包含 Tooltip。
|
||||||
|
- Canvas 旧逻辑用同一 coverage 同时决定“是否补画兄弟”和“是否刷新兄弟背景快照”。
|
||||||
|
- 最近相关改动:[可选] Tooltip coverage 纳入托管重绘链。
|
||||||
|
|
||||||
|
## 跟踪信息
|
||||||
|
|
||||||
|
- 首次发现时间: 2026-05-11
|
||||||
|
- 最后更新时间: 2026-05-11
|
||||||
|
- 修复版本: 当前工作区
|
||||||
|
- 验证版本: KEY1 ~ KEY6 编译通过,待用户手测
|
||||||
|
- 备注:[可选]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Fix-BUG-20260511-0009
|
||||||
|
|
||||||
|
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
|
||||||
|
|
||||||
|
## 关联信息
|
||||||
|
|
||||||
|
- Fix ID: Fix-BUG-20260511-0009
|
||||||
|
- 关联 BUG ID: BUG-20260511-0009
|
||||||
|
- 修复目标: 保证 `TabControl::indexOf()` 未命中时返回 `-1`
|
||||||
|
- 状态:已完成
|
||||||
|
- 负责人: Codex
|
||||||
|
- 分支 / 版本: 当前工作区
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
- 根因: `indexOf()` 循环结束后直接返回 `idx`,没有显式处理未命中分支。
|
||||||
|
- 触发条件: 查询不存在的页签文本。
|
||||||
|
- 为什么之前没发现:[可选] 当前测试更多关注页签切换行为,未覆盖未命中返回值。
|
||||||
|
- 关键证据:[可选] `TabControl.cpp` 旧实现未命中路径返回最后索引。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
- 修复思路: 未找到匹配项时显式返回 `-1`。
|
||||||
|
- 关键改动: 将 `TabControl::indexOf()` 末尾返回值从 `idx` 改为 `-1`。
|
||||||
|
- 涉及文件 / 类 / 函数: `TabControl.cpp::TabControl::indexOf`
|
||||||
|
- 影响的 API / 行为:[可选] 行为修正;符合函数语义。
|
||||||
|
- 关键约束 / 不变量:[可选] 找到时仍返回真实索引。
|
||||||
|
- 回滚点 / 开关:[可选] 无
|
||||||
|
|
||||||
|
## 影响评估
|
||||||
|
|
||||||
|
- 影响范围: 仅影响 `indexOf()` 未命中返回值。
|
||||||
|
- 兼容性影响:无
|
||||||
|
- 行为变化:有(未命中从最后索引修正为 `-1`)
|
||||||
|
- 性能影响:无
|
||||||
|
- 回归风险: 低
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 验证步骤:
|
||||||
|
|
||||||
|
1. 编译 `TabControl.cpp`。
|
||||||
|
2. 编译 `z-testDome.cpp` 的 `KEY1 ~ KEY6`。
|
||||||
|
3. 静态确认未命中路径返回 `-1`。
|
||||||
|
|
||||||
|
- 验证结果: 编译通过
|
||||||
|
- 回归检查:[可选] KEY1 ~ KEY6 编译级回归通过
|
||||||
|
- 验证证据:[可选]
|
||||||
|
|
||||||
|
## 落地信息
|
||||||
|
|
||||||
|
- Commit: 未提交
|
||||||
|
- PR:[可选]
|
||||||
|
- 发布版本:[可选]
|
||||||
|
- 备注:[可选]
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Fix-BUG-20260511-0010
|
||||||
|
|
||||||
|
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
|
||||||
|
|
||||||
|
## 关联信息
|
||||||
|
|
||||||
|
- Fix ID: Fix-BUG-20260511-0010
|
||||||
|
- 关联 BUG ID: BUG-20260511-0010
|
||||||
|
- 修复目标: 避免局部 root 内部 coverage 扩张后漏补 Dialog / 上层 overlay
|
||||||
|
- 状态:已完成
|
||||||
|
- 负责人: Codex
|
||||||
|
- 分支 / 版本: 当前工作区
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
- 根因: `commitManagedRepaint()` 只执行绘制,不返回本轮实际写屏 coverage;Window 仍按登记时的初始 coverage 进行上层补画判断。
|
||||||
|
- 触发条件: Canvas / TabControl 内部局部提交中补画了后序兄弟控件,实际写屏范围大于初始 source coverage。
|
||||||
|
- 为什么之前没发现:[可选] 需要 Dialog、Tooltip、后序兄弟补画三者同时满足。
|
||||||
|
- 关键证据:[可选] 临时日志中 `Canvas partial end coverage` 已与 Dialog 相交,但 `dialog overlay check` 使用旧 working coverage 结果为不相交。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
- 修复思路: 在 Window 层进行保守兜底。root 局部提交完成后,如果 root 仍支持局部提交,则将 root 当前 managed coverage 并入上层补画判断 coverage。
|
||||||
|
- 关键改动:
|
||||||
|
- `Window::flushManagedRepaint()` 在 `root->commitManagedRepaint()` 后合并 `root->getManagedRepaintCoverageRect()`。
|
||||||
|
- 只扩大上层补画判断,不改变 root 自身局部提交策略。
|
||||||
|
- 涉及文件 / 类 / 函数: `Window.cpp::flushManagedRepaint`
|
||||||
|
- 影响的 API / 行为:[可选] 无公开 API 变化。
|
||||||
|
- 关键约束 / 不变量:[可选] Dialog 仍保持顶层 overlay 语义。
|
||||||
|
- 回滚点 / 开关:[可选] 回退该 coverage 合并逻辑。
|
||||||
|
|
||||||
|
## 影响评估
|
||||||
|
|
||||||
|
- 影响范围: 托管重绘后的上层普通控件 / Dialog 补画判断。
|
||||||
|
- 兼容性影响:无
|
||||||
|
- 行为变化:有(Dialog / 上层控件补画更保守)
|
||||||
|
- 性能影响:有(可能多补画少量 overlay)
|
||||||
|
- 回归风险: 中低;主要风险是局部 root 大范围覆盖时上层补画次数增加。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 验证步骤:
|
||||||
|
|
||||||
|
1. 编译 `Window.cpp / Canvas.cpp`。
|
||||||
|
2. 编译 `z-testDome.cpp /DKEY=6`。
|
||||||
|
3. 编译 `z-testDome.cpp` 的 `KEY1 ~ KEY6`。
|
||||||
|
|
||||||
|
- 验证结果: 编译通过;临时日志已删除
|
||||||
|
- 回归检查:[可选] KEY6 非模态 Dialog + Tooltip 覆盖链待用户手测
|
||||||
|
- 验证证据:[可选]
|
||||||
|
|
||||||
|
## 落地信息
|
||||||
|
|
||||||
|
- Commit: 未提交
|
||||||
|
- PR:[可选]
|
||||||
|
- 发布版本:[可选]
|
||||||
|
- 备注: 下版本建议让 `commitManagedRepaint()` 返回 actual coverage,替代 root 全覆盖兜底。
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Fix-BUG-20260511-0011
|
||||||
|
|
||||||
|
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
|
||||||
|
|
||||||
|
## 关联信息
|
||||||
|
|
||||||
|
- Fix ID: Fix-BUG-20260511-0011
|
||||||
|
- 关联 BUG ID: BUG-20260511-0011
|
||||||
|
- 修复目标: 事件尾补收集跨 root dirty 子树,保证同轮托管提交
|
||||||
|
- 状态:已完成
|
||||||
|
- 负责人: Codex
|
||||||
|
- 分支 / 版本: 当前工作区
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
- 根因: `Window::requestManagedRepaint()` 只登记当前事件链触发的 source/root;回调里直接修改其他 root 的控件时,该 root 只变 dirty,没有进入 `managedRepaintItems`。
|
||||||
|
- 触发条件: 点击一个 root 中的控件,回调修改另一个 root 下的 Label / 控件状态。
|
||||||
|
- 为什么之前没发现:[可选] 需要跨 root 回调才能稳定暴露。
|
||||||
|
- 关键证据:[可选] 下一次鼠标消息后 Label 才刷新,说明目标 root dirty 状态滞留。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
- 修复思路: 在普通输入事件收口阶段、`flushManagedRepaint()` 前扫描所有顶层普通控件和可见 Dialog,发现未登记但存在 dirty 子树的 root,则补登记为托管重绘项。
|
||||||
|
- 关键改动:
|
||||||
|
- 新增 `Window::collectDirtyRootsForManagedRepaint()`。
|
||||||
|
- `runEventLoop()` 在 `flushManagedRepaint()` 前调用该函数。
|
||||||
|
- 涉及文件 / 类 / 函数:
|
||||||
|
- `Window.h`
|
||||||
|
- `Window.cpp::collectDirtyRootsForManagedRepaint`
|
||||||
|
- `Window.cpp::runEventLoop`
|
||||||
|
- 影响的 API / 行为:[可选] 无公开 API 变化。
|
||||||
|
- 关键约束 / 不变量:[可选] 只处理可见 root;已登记 root 不重复登记。
|
||||||
|
- 回滚点 / 开关:[可选] 移除事件尾补收集调用。
|
||||||
|
|
||||||
|
## 影响评估
|
||||||
|
|
||||||
|
- 影响范围: 普通输入事件尾的托管重绘收口。
|
||||||
|
- 兼容性影响:无
|
||||||
|
- 行为变化:有(跨 root dirty 会更及时刷新)
|
||||||
|
- 性能影响:有(事件尾多一次顶层 dirty 子树扫描)
|
||||||
|
- 回归风险: 中;如果存在历史残留 dirty,可能更早暴露重绘问题。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 验证步骤:
|
||||||
|
|
||||||
|
1. 编译 `Window.cpp`。
|
||||||
|
2. 编译 `z-testDome.cpp /DKEY=6`。
|
||||||
|
3. 编译 `z-testDome.cpp` 的 `KEY1 ~ KEY6`。
|
||||||
|
|
||||||
|
- 验证结果: 编译通过
|
||||||
|
- 回归检查:[可选] KEY6 Right 页内按钮更新 A 区状态 Label 待用户手测
|
||||||
|
- 验证证据:[可选]
|
||||||
|
|
||||||
|
## 落地信息
|
||||||
|
|
||||||
|
- Commit: 未提交
|
||||||
|
- PR:[可选]
|
||||||
|
- 发布版本:[可选]
|
||||||
|
- 备注:[可选]
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Fix-BUG-20260511-0012
|
||||||
|
|
||||||
|
> 适用场景:记录某个 BUG 的修复方案、影响评估、验证结果与落地信息。
|
||||||
|
|
||||||
|
## 关联信息
|
||||||
|
|
||||||
|
- Fix ID: Fix-BUG-20260511-0012
|
||||||
|
- 关联 BUG ID: BUG-20260511-0012
|
||||||
|
- 修复目标: 区分 Tooltip 临时浮层 coverage 与会污染背景快照的持久 coverage
|
||||||
|
- 状态:已完成
|
||||||
|
- 负责人: Codex
|
||||||
|
- 分支 / 版本: 当前工作区
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
- 根因: 托管重绘只有一类 coverage,既用于完整绘制范围判断,也用于兄弟控件背景快照刷新判断。Tooltip 属于临时浮层,不应进入后者。
|
||||||
|
- 触发条件: Tooltip coverage 覆盖到后序兄弟控件,兄弟控件被局部 overlay 补画。
|
||||||
|
- 为什么之前没发现:[可选] 需要 Tooltip 与透明 Label 或带快照兄弟控件重叠。
|
||||||
|
- 关键证据:[可选] 透明 Label 已设置 `setTextdisap(true)`,但 `draw()` 仍会先回贴背景快照。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
- 修复思路: 拆分完整 coverage 与持久 coverage。Tooltip 进入完整 coverage,用于 Window/Dialog overlay 判断;但不进入持久 coverage,避免兄弟控件刷新快照时捕获 Tooltip。
|
||||||
|
- 关键改动:
|
||||||
|
- `Control` 新增内部虚函数 `getManagedRepaintPersistentCoverageRect()`。
|
||||||
|
- `Button` 的完整 coverage 包含 Tooltip,持久 coverage 仅包含按钮本体。
|
||||||
|
- `Canvas / TabControl / Table` 递归合并子控件持久 coverage。
|
||||||
|
- `Canvas / TabControl` 局部提交中,只有命中持久 coverage 的兄弟控件才作废快照并补画。
|
||||||
|
- 只命中 Tooltip 等临时 coverage 的兄弟控件不再补画,避免透明控件回贴旧快照擦掉 Tooltip。
|
||||||
|
- 涉及文件 / 类 / 函数:
|
||||||
|
- `Control.h / Control.cpp`
|
||||||
|
- `Button.h / Button.cpp`
|
||||||
|
- `Canvas.h / Canvas.cpp`
|
||||||
|
- `TabControl.h / TabControl.cpp`
|
||||||
|
- `Table.h / Table.cpp`
|
||||||
|
- 影响的 API / 行为:[可选] 新增内部 virtual 接口,无公开用户 API 变化。
|
||||||
|
- 关键约束 / 不变量:[可选] Tooltip 智能选位仍不在本轮范围内。
|
||||||
|
- 回滚点 / 开关:[可选] 回退持久 coverage 拆分逻辑。
|
||||||
|
|
||||||
|
## 影响评估
|
||||||
|
|
||||||
|
- 影响范围: Tooltip、Canvas / TabControl 局部 overlay 补画、背景快照捕获。
|
||||||
|
- 兼容性影响:无
|
||||||
|
- 行为变化:有(Tooltip 作为临时浮层时,不再触发普通兄弟控件补画)
|
||||||
|
- 性能影响:轻微(多维护一组矩形并集和相交判断)
|
||||||
|
- 回归风险:
|
||||||
|
- 如果某控件持久 coverage 定义过小,可能漏补兄弟快照。
|
||||||
|
- Tooltip 会短暂显示在同容器后序普通控件上方,这是本轮有意选择。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 验证步骤:
|
||||||
|
|
||||||
|
1. 编译 `Control.cpp / Button.cpp / Canvas.cpp / TabControl.cpp / Table.cpp / Window.cpp`。
|
||||||
|
2. 编译 `z-testDome.cpp` 的 `KEY1 ~ KEY6`。
|
||||||
|
3. 手测 KEY6 A 区禁用按钮 Tooltip 与状态 Label 交叠。
|
||||||
|
4. 手测 TabControl 页签 / 页面内 Tooltip。
|
||||||
|
|
||||||
|
- 验证结果: 编译通过;GUI 行为待用户手测
|
||||||
|
- 回归检查:[可选] KEY6 A 区、KEY5 Tooltip/overlay、KEY4 Dialog overlay
|
||||||
|
- 验证证据:[可选]
|
||||||
|
|
||||||
|
## 落地信息
|
||||||
|
|
||||||
|
- Commit: 未提交
|
||||||
|
- PR:[可选]
|
||||||
|
- 发布版本:[可选]
|
||||||
|
- 备注: 下版本可进一步引入 actual coverage 返回值和正式 coverage 诊断日志。
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# 功能变更 ID: Feature-20260511-0012
|
||||||
|
|
||||||
|
> 适用场景:记录小到中等规模的功能、接口、行为、默认值或配置变化。
|
||||||
|
> 不适用场景:新增核心模块、重大模块重构、架构级设计,请使用“新增功能模块”模板。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
- ID: Feature-20260511-0012
|
||||||
|
- 标题: 发布前托管重绘与 KEY6 封版收口
|
||||||
|
- 状态:已完成
|
||||||
|
- 类型:修改
|
||||||
|
- 级别:L3 重大
|
||||||
|
- 模块: Window / Canvas / TabControl / Tooltip / Dialog / 测试用例
|
||||||
|
- 版本 / 分支: 当前工作区
|
||||||
|
- 环境: Windows / EasyX
|
||||||
|
- 负责人: Codex
|
||||||
|
|
||||||
|
## 变更背景
|
||||||
|
|
||||||
|
- 背景:
|
||||||
|
- 发布前封版阶段需要稳定现有布局、托管重绘、Dialog / overlay / hover 状态同步链路。
|
||||||
|
- KEY6 补充覆盖后暴露了非模态 Dialog、Tooltip、跨 root dirty、透明 Label 快照等组合问题。
|
||||||
|
- 目标:
|
||||||
|
- 修复确定性小问题。
|
||||||
|
- 收口 Dialog / overlay 后鼠标状态同步语义。
|
||||||
|
- 让 KEY6 覆盖已实现但原主回归未覆盖的分支。
|
||||||
|
- 修复托管局部重绘中 coverage 和背景快照语义混用导致的问题。
|
||||||
|
- 不做什么:[可选]
|
||||||
|
- 不实现焦点系统。
|
||||||
|
- 不实现键盘事件系统。
|
||||||
|
- 不做 Tooltip 智能选位。
|
||||||
|
- 不做 Table 纵向 Stretch。
|
||||||
|
- 不实现 Table 内部局部重绘体系。
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
|
||||||
|
- 变更摘要:
|
||||||
|
- 修复 `TabControl::indexOf()` 未命中返回值。
|
||||||
|
- 抽取 Dialog / overlay 后鼠标状态同步辅助函数。
|
||||||
|
- 新增 KEY6 分支覆盖用例。
|
||||||
|
- `Window` 增加局部 root 提交后 overlay coverage 保守兜底。
|
||||||
|
- `Window` 事件尾补收集跨 root dirty 子树。
|
||||||
|
- 托管重绘拆分完整 coverage 与持久 coverage,避免 Tooltip 污染背景快照。
|
||||||
|
- 新增项:[可选]
|
||||||
|
- `Window::syncMouseStateAfterOverlayChanged(...)`
|
||||||
|
- `Window::collectDirtyRootsForManagedRepaint()`
|
||||||
|
- `Control::getManagedRepaintPersistentCoverageRect()`
|
||||||
|
- KEY6 测试入口
|
||||||
|
- 修改项:[可选]
|
||||||
|
- `Canvas / TabControl` 局部 overlay 补画策略。
|
||||||
|
- `Button` Tooltip coverage 语义。
|
||||||
|
- `Table` 托管 coverage 语义补充。
|
||||||
|
- 删除 / 废弃项:[可选]
|
||||||
|
- 无
|
||||||
|
- 受影响的文件 / 类 / 函数:
|
||||||
|
- `Window.h / Window.cpp`
|
||||||
|
- `Control.h / Control.cpp`
|
||||||
|
- `Button.h / Button.cpp`
|
||||||
|
- `Canvas.h / Canvas.cpp`
|
||||||
|
- `TabControl.h / TabControl.cpp`
|
||||||
|
- `Table.h / Table.cpp`
|
||||||
|
- `z-testDome.cpp`
|
||||||
|
- 对外 API / 属性变化:[可选]
|
||||||
|
- 无公开用户 API 变化。
|
||||||
|
- 新增接口均为内部托管重绘语义。
|
||||||
|
|
||||||
|
## 行为对照
|
||||||
|
|
||||||
|
- 变更前:
|
||||||
|
- `indexOf()` 未命中返回最后索引。
|
||||||
|
- Dialog 关闭 / 非模态 Dialog 吞鼠标移动后的 synthetic move 逻辑散落在 `runEventLoop()`。
|
||||||
|
- 局部 root 内部 coverage 扩张后,Window 可能漏补 Dialog。
|
||||||
|
- 跨 root 回调改脏后,目标 root 可能等下一次事件才刷新。
|
||||||
|
- Tooltip 临时浮层与持久 coverage 混用,可能污染或擦除兄弟控件快照。
|
||||||
|
- 变更后:
|
||||||
|
- `indexOf()` 未命中返回 `-1`。
|
||||||
|
- overlay 后鼠标状态同步由语义函数集中处理。
|
||||||
|
- Window 对可局部提交 root 的上层补画判断更保守。
|
||||||
|
- 事件尾会补收集未登记但已 dirty 的 root。
|
||||||
|
- Tooltip 进入完整 coverage,但不进入持久 coverage;只命中临时浮层的兄弟控件不再补画。
|
||||||
|
- 兼容性说明:兼容
|
||||||
|
- 迁移说明:[可选]
|
||||||
|
- 用户代码无需迁移。
|
||||||
|
|
||||||
|
## 验证与落地
|
||||||
|
|
||||||
|
- 验证方式:
|
||||||
|
- 编译核心变更文件。
|
||||||
|
- 编译 `z-testDome.cpp` 的 `KEY1 ~ KEY6`。
|
||||||
|
- 根据用户手测反馈修正 KEY6 Tooltip / Dialog / Label 组合问题。
|
||||||
|
- 验证结果:
|
||||||
|
- `Control.cpp / Button.cpp / Canvas.cpp / TabControl.cpp / Table.cpp / Window.cpp` 编译通过。
|
||||||
|
- `KEY1 ~ KEY6` 编译通过。
|
||||||
|
- GUI 行为需继续由用户在本机手测确认。
|
||||||
|
- 关联 BUG / Fix:[可选]
|
||||||
|
- BUG-20260511-0009 / Fix-BUG-20260511-0009
|
||||||
|
- BUG-20260511-0010 / Fix-BUG-20260511-0010
|
||||||
|
- BUG-20260511-0011 / Fix-BUG-20260511-0011
|
||||||
|
- BUG-20260511-0012 / Fix-BUG-20260511-0012
|
||||||
|
- Commit: 未提交
|
||||||
|
- PR:[可选]
|
||||||
|
- 发布版本:[可选]
|
||||||
|
- 备注:[可选]
|
||||||
|
- 下版本建议引入 `commitManagedRepaint()` actual coverage 返回值,替代当前 root coverage 保守兜底。
|
||||||
|
- 下版本建议建立正式 coverage 诊断日志开关,避免再临时散加日志。
|
||||||
Reference in New Issue
Block a user