Finalize layout stage 2 fixes and refresh regression scenes
This commit is contained in:
+83
-1
@@ -432,6 +432,8 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
// 事件吞噬规则:
|
// 事件吞噬规则:
|
||||||
// - 鼠标移动:只有“当前命中按钮”时才吞掉,避免前一个按钮在清 hover 时截断消息,
|
// - 鼠标移动:只有“当前命中按钮”时才吞掉,避免前一个按钮在清 hover 时截断消息,
|
||||||
// 导致后一个真正命中的按钮收不到 WM_MOUSEMOVE。
|
// 导致后一个真正命中的按钮收不到 WM_MOUSEMOVE。
|
||||||
|
// 这里的前提是:父容器会保证“没有拿到真实消息的后续兄弟”还能走一遍
|
||||||
|
// clearTransientMouseState(),从而清掉旧 hover / tooltip。
|
||||||
// - 鼠标按下/抬起:命中按钮区域时吞掉,避免点击穿透到底层控件。
|
// - 鼠标按下/抬起:命中按钮区域时吞掉,避免点击穿透到底层控件。
|
||||||
if (msg.message == WM_MOUSEMOVE)
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
{
|
{
|
||||||
@@ -451,6 +453,79 @@ bool Button::handleEvent(const ExMessage& msg)
|
|||||||
return consume;
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Button::clearTransientMouseState()
|
||||||
|
{
|
||||||
|
if (!show)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool hoverChanged = false;
|
||||||
|
bool normalClickChanged = false;
|
||||||
|
bool bodyVisualChanged = false;
|
||||||
|
bool tooltipChanged = false;
|
||||||
|
|
||||||
|
if (hover)
|
||||||
|
{
|
||||||
|
hover = false;
|
||||||
|
hoverChanged = true;
|
||||||
|
bodyVisualChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NORMAL 模式下的按下态只属于一次鼠标交互过程;
|
||||||
|
// 如果本次 WM_MOUSEMOVE 已经被同层更上层的兄弟控件消费,
|
||||||
|
// 当前按钮就不应继续保留这笔临时按下视觉。
|
||||||
|
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
|
{
|
||||||
|
click = false;
|
||||||
|
normalClickChanged = true;
|
||||||
|
bodyVisualChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tipVisible)
|
||||||
|
{
|
||||||
|
hideTooltip();
|
||||||
|
tooltipChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool stateChanged = bodyVisualChanged || tooltipChanged;
|
||||||
|
if (!stateChanged)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SX_LOG_TRACE("Event")
|
||||||
|
<< SX_T("清理按钮鼠标临时状态:id=", "clear button transient mouse state: id=")
|
||||||
|
<< id
|
||||||
|
<< SX_T(" hover=", " hover=") << (hoverChanged ? 1 : 0)
|
||||||
|
<< SX_T(" tooltip=", " tooltip=") << (tooltipChanged ? 1 : 0)
|
||||||
|
<< SX_T(" normalClick=", " normalClick=") << (normalClickChanged ? 1 : 0);
|
||||||
|
|
||||||
|
markEventVisualChanged(true);
|
||||||
|
|
||||||
|
// 只有按钮本体视觉真正变化时,才请求按钮自身区域重绘;
|
||||||
|
// 若只是 Tooltip 消失,hideTooltip() 已经通过回贴快照清掉了悬浮层区域,
|
||||||
|
// 不再额外重绘按钮本体,避免高频鼠标移动时的无意义重画。
|
||||||
|
if (bodyVisualChanged)
|
||||||
|
{
|
||||||
|
dirty = true;
|
||||||
|
requestRepaint(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT Button::getManagedRepaintCoverageRect() const
|
||||||
|
{
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
|
||||||
|
if (!tipEnabled || !tipVisible)
|
||||||
|
return coverage;
|
||||||
|
|
||||||
|
const RECT tipRect = tipLabel.getManagedRepaintCoverageRect();
|
||||||
|
coverage.left = (std::min)(coverage.left, tipRect.left);
|
||||||
|
coverage.top = (std::min)(coverage.top, tipRect.top);
|
||||||
|
coverage.right = (std::max)(coverage.right, tipRect.right);
|
||||||
|
coverage.bottom = (std::max)(coverage.bottom, tipRect.bottom);
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
void Button::setOnClickListener(std::function<void()> callback)
|
void Button::setOnClickListener(std::function<void()> callback)
|
||||||
{
|
{
|
||||||
this->onClickCallback = std::move(callback);
|
this->onClickCallback = std::move(callback);
|
||||||
@@ -558,7 +633,14 @@ void Button::setButtonShape(StellarX::ControlShape shape)
|
|||||||
//允许通过外部函数修改按钮的点击状态,并执行相应的回调函数
|
//允许通过外部函数修改按钮的点击状态,并执行相应的回调函数
|
||||||
void Button::setButtonClick(BOOL click)
|
void Button::setButtonClick(BOOL click)
|
||||||
{
|
{
|
||||||
this->click = click;
|
const bool targetClick = (click != FALSE);
|
||||||
|
// TOGGLE 状态若没有发生变化,就不应重复触发 onToggleOn/OffCallback。
|
||||||
|
// 否则像 TabControl 这种“外部再次激活已激活页签”的场景,
|
||||||
|
// 会把页面显示/隐藏、快照失效、重绘链整条重复执行一遍,最终破坏可见页的背景恢复语义。
|
||||||
|
if (mode == StellarX::ButtonMode::TOGGLE && this->click == targetClick)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->click = targetClick;
|
||||||
|
|
||||||
if (mode == StellarX::ButtonMode::NORMAL && click)
|
if (mode == StellarX::ButtonMode::NORMAL && click)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,87 +101,94 @@ public:
|
|||||||
void draw() override;
|
void draw() override;
|
||||||
//按钮事件处理
|
//按钮事件处理
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
// 清理按钮的鼠标瞬时状态;用于父容器在 WM_MOUSEMOVE 短路后补做 hover/tooltip 收口。
|
||||||
|
bool clearTransientMouseState() override;
|
||||||
|
// Tooltip 可见时,按钮实际写像素范围不再等于按钮本体,需要把 Tooltip 矩形并入 coverage。
|
||||||
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
|
|
||||||
//设置回调函数
|
// 设置 NORMAL 模式下的点击回调
|
||||||
void setOnClickListener(std::function<void()> callback);
|
void setOnClickListener(std::function<void()> callback);
|
||||||
//设置TOGGLE模式下被点击的回调函数
|
// 设置 TOGGLE 模式下切换到打开状态时的回调
|
||||||
void setOnToggleOnListener(std::function<void()> callback);
|
void setOnToggleOnListener(std::function<void()> callback);
|
||||||
//设置TOGGLE模式下取消点击的回调函数
|
// 设置 TOGGLE 模式下切换到关闭状态时的回调
|
||||||
void setOnToggleOffListener(std::function<void()> callback);
|
void setOnToggleOffListener(std::function<void()> callback);
|
||||||
//设置按钮模式
|
// 设置按钮模式(NORMAL / TOGGLE / DISABLED)
|
||||||
void setbuttonMode(StellarX::ButtonMode mode);
|
void setbuttonMode(StellarX::ButtonMode mode);
|
||||||
//设置圆角矩形椭圆宽度
|
// 设置圆角矩形的圆角宽度
|
||||||
void setROUND_RECTANGLEwidth(int width);
|
void setROUND_RECTANGLEwidth(int width);
|
||||||
//设置圆角矩形椭圆高度
|
// 设置圆角矩形的圆角高度
|
||||||
void setROUND_RECTANGLEheight(int height);
|
void setROUND_RECTANGLEheight(int height);
|
||||||
//设置按钮填充模式
|
// 设置按钮填充模式
|
||||||
void setFillMode(StellarX::FillMode mode);
|
void setFillMode(StellarX::FillMode mode);
|
||||||
//设置按钮填充图案
|
// 设置按钮图案填充样式
|
||||||
void setFillIma(StellarX::FillStyle ima);
|
void setFillIma(StellarX::FillStyle ima);
|
||||||
//设置按钮填充图像
|
// 设置按钮图像填充资源
|
||||||
void setFillIma(std::string imaName);
|
void setFillIma(std::string imaName);
|
||||||
//设置按钮边框颜色
|
// 设置按钮边框颜色
|
||||||
void setButtonBorder(COLORREF Border);
|
void setButtonBorder(COLORREF Border);
|
||||||
//设置按钮未被点击颜色
|
// 设置按钮默认态颜色
|
||||||
void setButtonFalseColor(COLORREF color);
|
void setButtonFalseColor(COLORREF color);
|
||||||
//设置按钮文本
|
// 设置按钮文本(char* 重载)
|
||||||
void setButtonText(const char* text);
|
void setButtonText(const char* text);
|
||||||
|
// 设置按钮文本(std::string 重载)
|
||||||
void setButtonText(std::string text);
|
void setButtonText(std::string text);
|
||||||
//设置按钮形状
|
// 设置按钮几何形状
|
||||||
void setButtonShape(StellarX::ControlShape shape);
|
void setButtonShape(StellarX::ControlShape shape);
|
||||||
//设置按钮点击状态
|
// 直接设置按钮点击状态;TOGGLE 模式下会按状态变化触发相应回调
|
||||||
void setButtonClick(BOOL click);
|
void setButtonClick(BOOL click);
|
||||||
|
|
||||||
//判断按钮是否被点击
|
// 查询按钮当前是否处于点击/选中状态
|
||||||
bool isClicked() const;
|
bool isClicked() const;
|
||||||
|
|
||||||
//获取按钮文字
|
// 获取按钮文本
|
||||||
std::string getButtonText() const;
|
std::string getButtonText() const;
|
||||||
|
// 获取按钮文本的 C 字符串视图
|
||||||
const char* getButtonText_c() const;
|
const char* getButtonText_c() const;
|
||||||
//获取按钮模式
|
// 获取按钮模式
|
||||||
StellarX::ButtonMode getButtonMode() const;
|
StellarX::ButtonMode getButtonMode() const;
|
||||||
//获取按钮形状
|
// 获取按钮形状
|
||||||
StellarX::ControlShape getButtonShape() const;
|
StellarX::ControlShape getButtonShape() const;
|
||||||
//获取按钮填充模式
|
// 获取按钮填充模式
|
||||||
StellarX::FillMode getFillMode() const;
|
StellarX::FillMode getFillMode() const;
|
||||||
//获取按钮填充图案
|
// 获取按钮图案填充样式
|
||||||
StellarX::FillStyle getFillIma() const;
|
StellarX::FillStyle getFillIma() const;
|
||||||
//获取按钮填充图像
|
// 获取按钮图像填充资源
|
||||||
IMAGE* getFillImaImage() const;
|
IMAGE* getFillImaImage() const;
|
||||||
//获取按钮边框颜色
|
// 获取按钮边框颜色
|
||||||
COLORREF getButtonBorder() const;
|
COLORREF getButtonBorder() const;
|
||||||
//获取按钮文字颜色
|
// 获取按钮文字颜色
|
||||||
COLORREF getButtonTextColor() const;
|
COLORREF getButtonTextColor() const;
|
||||||
//获取按钮文字样式
|
// 获取按钮文字样式
|
||||||
StellarX::ControlText getButtonTextStyle() const;
|
StellarX::ControlText getButtonTextStyle() const;
|
||||||
public:
|
public:
|
||||||
// === Tooltip API===
|
// === Tooltip API===
|
||||||
//设置是否启用提示框
|
// 开关 Tooltip;关闭时只修改内部启用/显示状态,不负责智能选位
|
||||||
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
|
void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; }
|
||||||
//设置提示框延时
|
// 设置 Tooltip 延时(毫秒)
|
||||||
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); }
|
||||||
//设置提示框是否跟随鼠标
|
// 设置 Tooltip 是否跟随鼠标
|
||||||
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
void setTooltipFollowCursor(bool on) { tipFollowCursor = on; }
|
||||||
//设置提示框位置偏移
|
// 设置 Tooltip 相对鼠标/按钮的偏移量
|
||||||
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; }
|
||||||
//设置提示框样式
|
// 设置 Tooltip 的文字、背景和透明样式
|
||||||
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent);
|
||||||
//设置提示框文本
|
// 设置 NORMAL 模式下 Tooltip 文本
|
||||||
void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
|
void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; }
|
||||||
|
// 设置 TOGGLE 模式开/关两种状态下的 Tooltip 文本
|
||||||
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
|
void setTooltipTextsForToggle(const std::string& onText, const std::string& offText);
|
||||||
private:
|
private:
|
||||||
//初始化按钮
|
// 初始化按钮内部状态、颜色和 Tooltip 默认样式
|
||||||
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
|
void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch);
|
||||||
//判断鼠标是否在圆形按钮内
|
// 判断鼠标是否在圆形按钮内
|
||||||
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
|
bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius);
|
||||||
//判断鼠标是否在椭圆按钮内
|
// 判断鼠标是否在椭圆按钮内
|
||||||
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
|
bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height);
|
||||||
//获取对话框类型
|
//获取对话框类型
|
||||||
bool model() const override { return false; }
|
bool model() const override { return false; }
|
||||||
//文本截断
|
// 按当前按钮宽度和语言特征裁剪文本
|
||||||
void cutButtonText();
|
void cutButtonText();
|
||||||
// 统一隐藏&恢复背景
|
// 统一隐藏 Tooltip 并恢复其背景快照
|
||||||
void hideTooltip();
|
void hideTooltip();
|
||||||
// 根据当前 click 状态选择文案
|
// 根据当前 click 状态选择 Tooltip 文案
|
||||||
void refreshTooltipTextForState();
|
void refreshTooltipTextForState();
|
||||||
};
|
};
|
||||||
|
|||||||
+171
-13
@@ -20,16 +20,45 @@ static const char* SxCanvasMsgName(UINT m)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool SxCanvasRectValid(const RECT& rc)
|
||||||
|
{
|
||||||
|
return rc.right > rc.left && rc.bottom > rc.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SxCanvasRectsIntersect(const RECT& a, const RECT& b)
|
||||||
|
{
|
||||||
|
return a.left < b.right && a.right > b.left &&
|
||||||
|
a.top < b.bottom && a.bottom > b.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT SxCanvasUnionRect(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Canvas::Canvas()
|
Canvas::Canvas()
|
||||||
:Control(0, 0, 100, 100)
|
:Control(0, 0, 100, 100)
|
||||||
{
|
{
|
||||||
this->id = "Canvas";
|
this->id = "Canvas";
|
||||||
|
// Canvas 是通用容器,当前阶段显式允许双轴 Stretch。
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
|
this->layoutCapability.allowStretchY = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas::Canvas(int x, int y, int width, int height)
|
Canvas::Canvas(int x, int y, int width, int height)
|
||||||
:Control(x, y, width, height)
|
:Control(x, y, width, height)
|
||||||
{
|
{
|
||||||
this->id = "Canvas";
|
this->id = "Canvas";
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
|
this->layoutCapability.allowStretchY = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::relayoutManagedChildren()
|
void Canvas::relayoutManagedChildren()
|
||||||
@@ -162,19 +191,46 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
|||||||
bool anyVisualChanged = false;
|
bool anyVisualChanged = false;
|
||||||
Control* firstConsumer = nullptr;
|
Control* firstConsumer = nullptr;
|
||||||
|
|
||||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
{
|
{
|
||||||
Control* c = it->get();
|
// WM_MOUSEMOVE 需要特殊处理:
|
||||||
bool cConsumed = c->handleEvent(msg);
|
// - 第一个命中的兄弟分支收到真实消息;
|
||||||
|
// - 后续兄弟不再重新命中,只清理旧 hover / tooltip 等临时状态。
|
||||||
if (c->isDirty()) anyDirty = true;
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
if (c->didEventAffectVisual()) anyVisualChanged = true;
|
|
||||||
|
|
||||||
if (cConsumed)
|
|
||||||
{
|
{
|
||||||
firstConsumer = c;
|
Control* c = it->get();
|
||||||
consumed = true;
|
if (!consumed)
|
||||||
break;
|
{
|
||||||
|
bool cConsumed = c->handleEvent(msg);
|
||||||
|
if (c->didEventAffectVisual()) anyVisualChanged = true;
|
||||||
|
if (cConsumed)
|
||||||
|
{
|
||||||
|
firstConsumer = c;
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
Control* c = it->get();
|
||||||
|
bool cConsumed = c->handleEvent(msg);
|
||||||
|
|
||||||
|
if (c->isDirty()) anyDirty = true;
|
||||||
|
if (c->didEventAffectVisual()) anyVisualChanged = true;
|
||||||
|
|
||||||
|
if (cConsumed)
|
||||||
|
{
|
||||||
|
firstConsumer = c;
|
||||||
|
consumed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +254,20 @@ bool Canvas::handleEvent(const ExMessage& msg)
|
|||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Canvas::clearTransientMouseState()
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
Control* child = it->get();
|
||||||
|
if (!child->IsVisible())
|
||||||
|
continue;
|
||||||
|
if (child->clearTransientMouseState())
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Canvas::addControl(std::unique_ptr<Control> control)
|
void Canvas::addControl(std::unique_ptr<Control> control)
|
||||||
{
|
{
|
||||||
@@ -333,9 +403,68 @@ 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{};
|
||||||
|
bool hasCoverage = false;
|
||||||
|
auto commitManagedChild = [&](Control* child, bool forceOverlayRedraw)
|
||||||
|
{
|
||||||
|
if (!child || !child->IsVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool directDirty = child->isDirty();
|
||||||
|
const bool subtreeDirty = child->hasManagedDirtySubtree();
|
||||||
|
|
||||||
|
if (forceOverlayRedraw)
|
||||||
|
{
|
||||||
|
// overlay 补画必须先作废旧快照:
|
||||||
|
// 下层兄弟刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。
|
||||||
|
child->invalidateBackgroundSnapshot();
|
||||||
|
child->setDirty(true);
|
||||||
|
child->draw();
|
||||||
|
}
|
||||||
|
else if (directDirty)
|
||||||
|
{
|
||||||
|
child->draw();
|
||||||
|
}
|
||||||
|
else if (subtreeDirty)
|
||||||
|
{
|
||||||
|
// 这次真正脏的是“child 下面的子树”,而不是 child 自身。
|
||||||
|
// 例如嵌套 Canvas 中,第二层/第三层按钮脏了,但第一层 Canvas 自己并不 dirty。
|
||||||
|
// 这里必须把这条直接子分支提交下去,否则深层按钮状态永远没有机会被真正画出来。
|
||||||
|
child->commitManagedRepaint();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT childRect = child->getManagedRepaintCoverageRect();
|
||||||
|
if (!hasCoverage)
|
||||||
|
{
|
||||||
|
coverage = childRect;
|
||||||
|
hasCoverage = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coverage = SxCanvasUnionRect(coverage, childRect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
if (control->isDirty() && control->IsVisible())
|
{
|
||||||
control->draw();
|
Control* child = control.get();
|
||||||
|
if (!child->IsVisible())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (child->hasManagedDirtySubtree())
|
||||||
|
{
|
||||||
|
commitManagedChild(child, false);
|
||||||
|
}
|
||||||
|
else if (hasCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), coverage))
|
||||||
|
{
|
||||||
|
// 位于本次累计 coverage 上方、且发生相交的兄弟控件,需要补画回最上层。
|
||||||
|
commitManagedChild(child, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -344,6 +473,35 @@ void Canvas::requestRepaint(Control* parent)
|
|||||||
onRequestRepaintAsRoot();
|
onRequestRepaintAsRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Canvas::hasManagedDirtySubtree() const
|
||||||
|
{
|
||||||
|
if (dirty)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (const auto& child : controls)
|
||||||
|
{
|
||||||
|
if (!child->IsVisible())
|
||||||
|
continue;
|
||||||
|
if (child->hasManagedDirtySubtree())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT Canvas::getManagedRepaintCoverageRect() const
|
||||||
|
{
|
||||||
|
// Canvas::draw() 会先写自身背景,再强制绘制全部可见子控件。
|
||||||
|
// 因此它的实际 coverage 不能只看本体矩形,还要把可见子控件 coverage 递归并入。
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
for (const auto& child : controls)
|
||||||
|
{
|
||||||
|
if (!child->IsVisible())
|
||||||
|
continue;
|
||||||
|
coverage = SxCanvasUnionRect(coverage, child->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
return coverage;
|
||||||
|
}
|
||||||
|
|
||||||
bool Canvas::canCommitManagedPartialRepaint() const
|
bool Canvas::canCommitManagedPartialRepaint() const
|
||||||
{
|
{
|
||||||
// Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时,
|
// Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时,
|
||||||
|
|||||||
@@ -37,19 +37,27 @@ protected:
|
|||||||
// 清除所有子控件
|
// 清除所有子控件
|
||||||
void clearAllControls();
|
void clearAllControls();
|
||||||
public:
|
public:
|
||||||
|
// 默认构造:创建一个基础矩形容器
|
||||||
Canvas();
|
Canvas();
|
||||||
|
// 指定初始位置和尺寸构造容器
|
||||||
Canvas(int x, int y, int width, int height);
|
Canvas(int x, int y, int width, int height);
|
||||||
~Canvas() {}
|
~Canvas() {}
|
||||||
|
|
||||||
|
// 修改容器运行态 X,并同步重算全部受管理子控件的世界坐标
|
||||||
void setX(int x)override;
|
void setX(int x)override;
|
||||||
|
// 修改容器运行态 Y,并同步重算全部受管理子控件的世界坐标
|
||||||
void setY(int y)override;
|
void setY(int y)override;
|
||||||
|
// 修改容器运行态宽度,并同步重算全部受管理子控件的世界坐标
|
||||||
void setWidth(int width) override;
|
void setWidth(int width) override;
|
||||||
|
// 修改容器运行态高度,并同步重算全部受管理子控件的世界坐标
|
||||||
void setHeight(int height) override;
|
void setHeight(int height) override;
|
||||||
|
|
||||||
//绘制容器及其子控件
|
//绘制容器及其子控件
|
||||||
void draw() override;
|
void draw() override;
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
//添加控件
|
// 递归清理子树中的 hover / tooltip / 临时按下态,不把 Canvas 自己升级为整块重绘源。
|
||||||
|
bool clearTransientMouseState() override;
|
||||||
|
// 添加一个由 Canvas 托管的子控件
|
||||||
void addControl(std::unique_ptr<Control> control);
|
void addControl(std::unique_ptr<Control> control);
|
||||||
//设置容器样式
|
//设置容器样式
|
||||||
void setShape(StellarX::ControlShape shape);
|
void setShape(StellarX::ControlShape shape);
|
||||||
@@ -69,11 +77,13 @@ public:
|
|||||||
void onWindowResize() override;
|
void onWindowResize() override;
|
||||||
// 托管模式下登记为 root;非托管模式下走局部或根级重绘
|
// 托管模式下登记为 root;非托管模式下走局部或根级重绘
|
||||||
void requestRepaint(Control* parent)override;
|
void requestRepaint(Control* parent)override;
|
||||||
|
bool hasManagedDirtySubtree() const override;
|
||||||
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
// 判断当前 Canvas 是否可安全做局部提交
|
// 判断当前 Canvas 是否可安全做局部提交
|
||||||
bool canCommitManagedPartialRepaint() const override;
|
bool canCommitManagedPartialRepaint() const override;
|
||||||
// 托管收口阶段执行 Canvas 的真正重绘
|
// 托管收口阶段执行 Canvas 的真正重绘
|
||||||
void commitManagedRepaint() override;
|
void commitManagedRepaint() override;
|
||||||
//获取子控件列表
|
// 获取直接子控件列表
|
||||||
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
|
std::vector<std::unique_ptr<Control>>& getControls() { return controls; }
|
||||||
protected:
|
protected:
|
||||||
// 统一解算后,按当前运行态矩形把所有受管理子控件重新映射到新的世界坐标。
|
// 统一解算后,按当前运行态矩形把所有受管理子控件重新映射到新的世界坐标。
|
||||||
|
|||||||
+72
@@ -133,6 +133,35 @@ namespace
|
|||||||
result.pos = childDesignPos;
|
result.pos = childDesignPos;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* SxAxisName(bool horizontal)
|
||||||
|
{
|
||||||
|
return horizontal ? "X" : "Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogAxisDowngradeIfNeeded(const std::string& id, bool horizontal,
|
||||||
|
const StellarX::AxisLayoutSpec& spec, bool allowStretch)
|
||||||
|
{
|
||||||
|
if (spec.sizePolicy != StellarX::AxisSizePolicy::Stretch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!spec.anchorStart || !spec.anchorEnd)
|
||||||
|
{
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("布局降级:id=", "layout downgrade: id=") << id
|
||||||
|
<< SX_T(" axis=", " axis=") << SxAxisName(horizontal)
|
||||||
|
<< SX_T(" 原因=Stretch 但未同时锚定起止边", " reason=stretch without dual anchors");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowStretch)
|
||||||
|
{
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("能力边界拦截:id=", "layout capability intercept: id=") << id
|
||||||
|
<< SX_T(" axis=", " axis=") << SxAxisName(horizontal)
|
||||||
|
<< SX_T(" 原因=当前控件禁止该轴 Stretch", " reason=stretch disabled by capability");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||||||
@@ -270,15 +299,31 @@ void Control::setHeight(int height)
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Control::clearTransientMouseState()
|
||||||
|
{
|
||||||
|
// 基类默认没有 hover / tooltip / 临时按下态等鼠标瞬时状态。
|
||||||
|
// 具体控件若需要在“未收到本次 WM_MOUSEMOVE”时做清理,重写此接口即可。
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Control::commitCurrentGeometryAsDesignRect()
|
void Control::commitCurrentGeometryAsDesignRect()
|
||||||
{
|
{
|
||||||
// 该接口是“显式提交新的设计基线”的唯一入口之一。
|
// 该接口是“显式提交新的设计基线”的唯一入口之一。
|
||||||
// 普通布局解算、父容器重排、窗口 resize 均不得自动回写 local*,
|
// 普通布局解算、父容器重排、窗口 resize 均不得自动回写 local*,
|
||||||
// 否则会导致设计基线漂移,后续解算越来越不稳定。
|
// 否则会导致设计基线漂移,后续解算越来越不稳定。
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("提交设计基线:id=", "commit design rect: id=") << id
|
||||||
|
<< SX_T(" world=(", " world=(") << x << "," << y << "," << width << "," << height << ")"
|
||||||
|
<< SX_T(" parentLocalBefore=(", " parentLocalBefore=(")
|
||||||
|
<< localx << "," << localy << "," << localWidth << "," << localHeight << ")";
|
||||||
localx = parent ? (x - parent->getX()) : x;
|
localx = parent ? (x - parent->getX()) : x;
|
||||||
localy = parent ? (y - parent->getY()) : y;
|
localy = parent ? (y - parent->getY()) : y;
|
||||||
localWidth = width;
|
localWidth = width;
|
||||||
localHeight = height;
|
localHeight = height;
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("提交设计基线完成:id=", "commit design rect done: id=") << id
|
||||||
|
<< SX_T(" parentLocalAfter=(", " parentLocalAfter=(")
|
||||||
|
<< localx << "," << localy << "," << localWidth << "," << localHeight << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarX::ResolvedLayoutRect Control::resolveLayoutRect(int parentDesignW, int parentDesignH,
|
StellarX::ResolvedLayoutRect Control::resolveLayoutRect(int parentDesignW, int parentDesignH,
|
||||||
@@ -299,6 +344,8 @@ StellarX::ResolvedLayoutRect Control::resolveLayoutRect(int parentDesignW, int p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 第 1 层:先在父局部坐标系内分别解算水平轴和垂直轴。
|
// 第 1 层:先在父局部坐标系内分别解算水平轴和垂直轴。
|
||||||
|
LogAxisDowngradeIfNeeded(id, true, layoutSpec.horizontal, layoutCapability.allowStretchX);
|
||||||
|
LogAxisDowngradeIfNeeded(id, false, layoutSpec.vertical, layoutCapability.allowStretchY);
|
||||||
const LayoutAxisResult horizontal = ResolveAxis(parentDesignW, parentCurrentW, localx, localWidth,
|
const LayoutAxisResult horizontal = ResolveAxis(parentDesignW, parentCurrentW, localx, localWidth,
|
||||||
layoutSpec.horizontal, layoutCapability.allowStretchX);
|
layoutSpec.horizontal, layoutCapability.allowStretchX);
|
||||||
const LayoutAxisResult vertical = ResolveAxis(parentDesignH, parentCurrentH, localy, localHeight,
|
const LayoutAxisResult vertical = ResolveAxis(parentDesignH, parentCurrentH, localy, localHeight,
|
||||||
@@ -436,6 +483,17 @@ Control* Control::getManagedRepaintRoot()
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Control* Control::getManagedRepaintDirectBranch(Control* root)
|
||||||
|
{
|
||||||
|
if (!root)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
Control* branch = this;
|
||||||
|
while (branch->parent && branch->parent != root)
|
||||||
|
branch = branch->parent;
|
||||||
|
return branch;
|
||||||
|
}
|
||||||
|
|
||||||
RECT Control::getBoundsRect() const
|
RECT Control::getBoundsRect() const
|
||||||
{
|
{
|
||||||
RECT rc{};
|
RECT rc{};
|
||||||
@@ -446,6 +504,13 @@ RECT Control::getBoundsRect() const
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RECT Control::getManagedRepaintCoverageRect() const
|
||||||
|
{
|
||||||
|
// 基类默认认为“实际绘制 coverage == 控件本体矩形”。
|
||||||
|
// 复合控件或带浮层的控件会 override,把附加绘制区域一并并入。
|
||||||
|
return getBoundsRect();
|
||||||
|
}
|
||||||
|
|
||||||
bool Control::canCommitManagedPartialRepaint() const
|
bool Control::canCommitManagedPartialRepaint() const
|
||||||
{
|
{
|
||||||
// 基类默认不承诺自己能安全做局部提交;
|
// 基类默认不承诺自己能安全做局部提交;
|
||||||
@@ -453,6 +518,13 @@ bool Control::canCommitManagedPartialRepaint() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Control::hasManagedDirtySubtree() const
|
||||||
|
{
|
||||||
|
// 基类只感知自身是否为脏;
|
||||||
|
// 容器类会 override 为“自身或其受管理子树是否有脏内容”。
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
void Control::commitManagedRepaint()
|
void Control::commitManagedRepaint()
|
||||||
{
|
{
|
||||||
if (!show)
|
if (!show)
|
||||||
|
|||||||
@@ -124,6 +124,12 @@ public:
|
|||||||
int getLocalRight() const { return localx + localWidth; }
|
int getLocalRight() const { return localx + localWidth; }
|
||||||
int getLocalBottom() const { return localy + localHeight; }
|
int getLocalBottom() const { return localy + localHeight; }
|
||||||
|
|
||||||
|
// 公开几何 setter:
|
||||||
|
// - 面向用户使用
|
||||||
|
// - 默认只修改运行态矩形
|
||||||
|
// - 不允许自动回写设计基线 local*
|
||||||
|
// 若某个复合控件需要在 setter 后维护自己拥有的运行态子树一致性,
|
||||||
|
// 由该控件在 override 中自行处理,但仍不得越权承担统一布局器职责。
|
||||||
virtual void setX(int x);
|
virtual void setX(int x);
|
||||||
virtual void setY(int y);
|
virtual void setY(int y);
|
||||||
virtual void setWidth(int width);
|
virtual void setWidth(int width);
|
||||||
@@ -132,6 +138,10 @@ public:
|
|||||||
|
|
||||||
virtual void draw() = 0;
|
virtual void draw() = 0;
|
||||||
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费
|
||||||
|
// 轻量清理接口:仅用于“没有机会收到本次 WM_MOUSEMOVE”的兄弟分支,
|
||||||
|
// 清掉 hover / tooltip / 临时按下态等鼠标瞬时状态。
|
||||||
|
// 该接口不是事件分发入口,不参与新的命中判断,也不允许回写设计基线。
|
||||||
|
virtual bool clearTransientMouseState();
|
||||||
//设置是否显示
|
//设置是否显示
|
||||||
virtual void setIsVisible(bool show);
|
virtual void setIsVisible(bool show);
|
||||||
//设置父容器指针
|
//设置父容器指针
|
||||||
@@ -140,8 +150,14 @@ public:
|
|||||||
virtual void setHostWindow(Window* host) { this->hostWindow = host; }
|
virtual void setHostWindow(Window* host) { this->hostWindow = host; }
|
||||||
Window* getHostWindow() const; // 获取宿主 Window;子控件可沿 parent 向上回溯
|
Window* getHostWindow() const; // 获取宿主 Window;子控件可沿 parent 向上回溯
|
||||||
RECT getBoundsRect() const; // 获取当前控件外接矩形,用于覆盖/相交判断
|
RECT getBoundsRect() const; // 获取当前控件外接矩形,用于覆盖/相交判断
|
||||||
|
// 获取本控件“本轮实际可能写到屏幕上的覆盖范围”。
|
||||||
|
// 默认等于控件本体矩形;像 Button 这类会额外绘制 Tooltip 的控件,可 override 后扩大范围。
|
||||||
|
// 托管重绘 coverage、overlay 相交判断统一走这个接口,而不再默认使用控件本体 bounds。
|
||||||
|
virtual RECT getManagedRepaintCoverageRect() const;
|
||||||
Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root
|
Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root
|
||||||
|
Control* getManagedRepaintDirectBranch(Control* root); // 找到“root 下面承接本次脏变化的直接子分支”
|
||||||
bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照
|
bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照
|
||||||
|
virtual bool hasManagedDirtySubtree() const; // 当前控件或其受管理子树中是否存在待提交的脏内容
|
||||||
virtual bool canCommitManagedPartialRepaint() const; // 当前 root 是否可安全做“局部提交”而非整 root 重画
|
virtual bool canCommitManagedPartialRepaint() const; // 当前 root 是否可安全做“局部提交”而非整 root 重画
|
||||||
virtual void commitManagedRepaint(); // 托管收口阶段真正执行绘制的入口
|
virtual void commitManagedRepaint(); // 托管收口阶段真正执行绘制的入口
|
||||||
//设置是否重绘
|
//设置是否重绘
|
||||||
@@ -157,16 +173,25 @@ public:
|
|||||||
//用来检查对话框是否模态,其他控件不用实现
|
//用来检查对话框是否模态,其他控件不用实现
|
||||||
virtual bool model()const = 0;
|
virtual bool model()const = 0;
|
||||||
//布局
|
//布局
|
||||||
|
// 设置旧版布局模式(兼容入口)
|
||||||
void setLayoutMode(StellarX::LayoutMode layoutMode_);
|
void setLayoutMode(StellarX::LayoutMode layoutMode_);
|
||||||
|
// 设置旧版双锚点输入,并映射到内部统一 LayoutSpec
|
||||||
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
|
void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2);
|
||||||
|
// 获取旧版锚点 1(兼容读取入口)
|
||||||
StellarX::Anchor getAnchor_1() const;
|
StellarX::Anchor getAnchor_1() const;
|
||||||
|
// 获取旧版锚点 2(兼容读取入口)
|
||||||
StellarX::Anchor getAnchor_2() const;
|
StellarX::Anchor getAnchor_2() const;
|
||||||
|
// 获取旧版布局模式
|
||||||
StellarX::LayoutMode getLayoutMode() const;
|
StellarX::LayoutMode getLayoutMode() const;
|
||||||
// 获取内部统一布局规格;供 Window / Canvas 等统一解算入口使用。
|
// 获取内部统一布局规格;供 Window / Canvas 等统一解算入口使用。
|
||||||
const StellarX::LayoutSpec& getLayoutSpec() const;
|
const StellarX::LayoutSpec& getLayoutSpec() const;
|
||||||
// 获取控件能力边界;用于判断某个轴是否允许 Stretch。
|
// 获取控件能力边界;用于判断某个轴是否允许 Stretch。
|
||||||
const StellarX::LayoutCapability& getLayoutCapability() const;
|
const StellarX::LayoutCapability& getLayoutCapability() const;
|
||||||
// 显式将当前运行态矩形提交为新的设计基线。普通 resize / 重排过程中不得自动调用。
|
// 显式将当前运行态矩形提交为新的设计基线。
|
||||||
|
// 这是“运行态 -> 设计态”的受控入口之一:
|
||||||
|
// - 普通 resize / 重排过程中不得自动调用
|
||||||
|
// - 公开 setter 也不得隐式调用
|
||||||
|
// - 仅用于少数需要把当前结果固化为新设计基线的场景
|
||||||
void commitCurrentGeometryAsDesignRect();
|
void commitCurrentGeometryAsDesignRect();
|
||||||
protected:
|
protected:
|
||||||
// 第 1 层:根据父设计尺寸、父当前尺寸和本控件设计矩形,解算出当前运行态局部矩形。
|
// 第 1 层:根据父设计尺寸、父当前尺寸和本控件设计矩形,解算出当前运行态局部矩形。
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::Messa
|
|||||||
: Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
: Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text)
|
||||||
{
|
{
|
||||||
this->id = "Dialog";
|
this->id = "Dialog";
|
||||||
|
// Dialog 当前阶段只做“居中 + 自管内部 chrome”,
|
||||||
|
// 不参与外部 Stretch;宿主窗口变化时只重新居中。
|
||||||
|
this->layoutCapability.allowStretchX = false;
|
||||||
|
this->layoutCapability.allowStretchY = false;
|
||||||
setHostWindow(&hWnd);
|
setHostWindow(&hWnd);
|
||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,18 +63,19 @@ class Dialog : public Canvas
|
|||||||
bool pendingCleanup = false; //延迟清理
|
bool pendingCleanup = false; //延迟清理
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; // 字体样式
|
StellarX::ControlText textStyle; // 字体样式
|
||||||
// 清理方法声明
|
// 在事件安全点执行延迟清理;用于非模态关闭后统一回收
|
||||||
void performDelayedCleanup();
|
void performDelayedCleanup();
|
||||||
//工厂模式下调用非模态对话框时用来返回结果
|
// 非模态工厂调用时用于回传结果
|
||||||
std::function<void(StellarX::MessageBoxResult)> resultCallback;
|
std::function<void(StellarX::MessageBoxResult)> resultCallback;
|
||||||
//设置非模态获取结果的回调函数
|
// 设置非模态结果回调
|
||||||
void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb);
|
void SetResultCallback(std::function<void(StellarX::MessageBoxResult)> cb);
|
||||||
//获取对话框消息,用以去重
|
// 获取标题文本,用于非模态去重
|
||||||
std::string GetCaption() const;
|
std::string GetCaption() const;
|
||||||
//获取对话框消息,用以去重
|
// 获取正文文本,用于非模态去重
|
||||||
std::string GetText() const;
|
std::string GetText() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// 构造对话框;modal=false 时按非模态浮层工作
|
||||||
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true);
|
||||||
~Dialog();
|
~Dialog();
|
||||||
//绘制对话框
|
//绘制对话框
|
||||||
@@ -94,7 +95,7 @@ public:
|
|||||||
// 获取对话框结果
|
// 获取对话框结果
|
||||||
StellarX::MessageBoxResult GetResult() const;
|
StellarX::MessageBoxResult GetResult() const;
|
||||||
|
|
||||||
//获取对话框类型
|
// 返回当前对话框是否为模态
|
||||||
bool model() const override;
|
bool model() const override;
|
||||||
|
|
||||||
// 显示对话框
|
// 显示对话框
|
||||||
@@ -119,13 +120,14 @@ private:
|
|||||||
void initDialogSize();
|
void initDialogSize();
|
||||||
// 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮
|
// 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮
|
||||||
void rebuildChrome();
|
void rebuildChrome();
|
||||||
|
// 禁止走普通 Canvas 添加路径;Dialog 内部控件由自身 chrome 重建逻辑统一维护
|
||||||
void addControl(std::unique_ptr<Control> control);
|
void addControl(std::unique_ptr<Control> control);
|
||||||
bool canCommitManagedPartialRepaint() const override; // 判断当前 Dialog 是否可安全做局部提交
|
bool canCommitManagedPartialRepaint() const override; // 判断当前 Dialog 是否可安全做局部提交
|
||||||
void commitManagedRepaint() override; // 托管收口阶段执行 Dialog 的真正重绘
|
void commitManagedRepaint() override; // 托管收口阶段执行 Dialog 的真正重绘
|
||||||
|
|
||||||
// 清除所有控件
|
// 清除所有控件
|
||||||
void clearControls();
|
void clearControls();
|
||||||
//创建对话框按钮
|
// 创建标准对话框功能按钮
|
||||||
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
|
std::unique_ptr<Button> createDialogButton(int x, int y, const std::string& text);
|
||||||
void requestRepaint(Control* parent) override; // 托管模式下登记为 Dialog root;非托管模式下立即更新内部按钮
|
void requestRepaint(Control* parent) override; // 托管模式下登记为 Dialog root;非托管模式下立即更新内部按钮
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,81 @@
|
|||||||
#include "Label.h"
|
#include "Label.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Label 的内容尺寸刷新不依赖 EasyX 当前绘图上下文,
|
||||||
|
// 直接使用 GDI 依据“文本 + 字体样式”测量宽高。
|
||||||
|
// 这样无论是文本变化、字体样式变化,还是控件在窗口正式绘制前进入 dirty,
|
||||||
|
// 都可以先把运行态尺寸收口好,而不必等到 draw() 再决定几何。
|
||||||
|
void MeasureLabelText(const std::string& text, const StellarX::ControlText& style, int& width, int& height)
|
||||||
|
{
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
|
||||||
|
HDC hdc = CreateCompatibleDC(nullptr);
|
||||||
|
if (!hdc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOGFONTA fontDesc{};
|
||||||
|
fontDesc.lfHeight = style.nHeight;
|
||||||
|
fontDesc.lfWidth = style.nWidth;
|
||||||
|
fontDesc.lfEscapement = style.nEscapement;
|
||||||
|
fontDesc.lfOrientation = style.nOrientation;
|
||||||
|
fontDesc.lfWeight = style.nWeight;
|
||||||
|
fontDesc.lfItalic = style.bItalic ? TRUE : FALSE;
|
||||||
|
fontDesc.lfUnderline = style.bUnderline ? TRUE : FALSE;
|
||||||
|
fontDesc.lfStrikeOut = style.bStrikeOut ? TRUE : FALSE;
|
||||||
|
fontDesc.lfCharSet = DEFAULT_CHARSET;
|
||||||
|
fontDesc.lfOutPrecision = OUT_DEFAULT_PRECIS;
|
||||||
|
fontDesc.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||||
|
fontDesc.lfQuality = DEFAULT_QUALITY;
|
||||||
|
fontDesc.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
|
||||||
|
lstrcpynA(fontDesc.lfFaceName, style.lpszFace ? style.lpszFace : "微软雅黑", LF_FACESIZE);
|
||||||
|
|
||||||
|
HFONT font = CreateFontIndirectA(&fontDesc);
|
||||||
|
HFONT oldFont = nullptr;
|
||||||
|
if (font)
|
||||||
|
oldFont = (HFONT)SelectObject(hdc, font);
|
||||||
|
|
||||||
|
if (text.empty())
|
||||||
|
{
|
||||||
|
TEXTMETRICA tm{};
|
||||||
|
if (GetTextMetricsA(hdc, &tm))
|
||||||
|
height = tm.tmHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SIZE size{};
|
||||||
|
if (GetTextExtentPoint32A(hdc, text.c_str(), (int)text.size(), &size))
|
||||||
|
{
|
||||||
|
width = size.cx;
|
||||||
|
height = size.cy;
|
||||||
|
}
|
||||||
|
if (height == 0)
|
||||||
|
{
|
||||||
|
TEXTMETRICA tm{};
|
||||||
|
if (GetTextMetricsA(hdc, &tm))
|
||||||
|
height = tm.tmHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font)
|
||||||
|
{
|
||||||
|
if (oldFont)
|
||||||
|
SelectObject(hdc, oldFont);
|
||||||
|
DeleteObject(font);
|
||||||
|
}
|
||||||
|
DeleteDC(hdc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Label::Label()
|
Label::Label()
|
||||||
:Control(0, 0, 0, 0)
|
:Control(0, 0, 0, 0)
|
||||||
{
|
{
|
||||||
this->id = "Label";
|
this->id = "Label";
|
||||||
|
// Label 当前阶段明确走“内容驱动尺寸”语义:
|
||||||
|
// 外部布局器只安置位置,不通过 Stretch 改写它的宽高。
|
||||||
|
this->layoutCapability.allowStretchX = false;
|
||||||
|
this->layoutCapability.allowStretchY = false;
|
||||||
this->text = "默认标签";
|
this->text = "默认标签";
|
||||||
textStyle.color = RGB(0, 0, 0);
|
textStyle.color = RGB(0, 0, 0);
|
||||||
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
textBkColor = RGB(255, 255, 255);; //默认白色背景
|
||||||
@@ -13,11 +85,44 @@ Label::Label(int x, int y, std::string text, COLORREF textcolor, COLORREF bkColo
|
|||||||
:Control(x, y, 0, 0)
|
:Control(x, y, 0, 0)
|
||||||
{
|
{
|
||||||
this->id = "Label";
|
this->id = "Label";
|
||||||
|
this->layoutCapability.allowStretchX = false;
|
||||||
|
this->layoutCapability.allowStretchY = false;
|
||||||
this->text = text;
|
this->text = text;
|
||||||
textStyle.color = textcolor;
|
textStyle.color = textcolor;
|
||||||
textBkColor = bkColor; //默认白色背景
|
textBkColor = bkColor; //默认白色背景
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Label::refreshContentDrivenRuntimeGeometry()
|
||||||
|
{
|
||||||
|
// Label 的尺寸来源于“当前文本 + 当前字体样式”。
|
||||||
|
// 这里只更新运行态 width/height,不自动回写 localWidth/localHeight,
|
||||||
|
// 避免内容变化把设计基线偷偷带偏。
|
||||||
|
if (contentMeasureValid &&
|
||||||
|
lastMeasuredText == text &&
|
||||||
|
!(lastMeasuredStyle != textStyle))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newWidth = 0;
|
||||||
|
int newHeight = 0;
|
||||||
|
MeasureLabelText(text, textStyle, newWidth, newHeight);
|
||||||
|
|
||||||
|
const bool sizeChanged = (newWidth != this->width) || (newHeight != this->height);
|
||||||
|
if (sizeChanged && hasSnap)
|
||||||
|
{
|
||||||
|
// 运行态尺寸变化时,先恢复旧背景,避免文本缩短后旧像素残留在屏幕上。
|
||||||
|
discardBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->width = newWidth;
|
||||||
|
this->height = newHeight;
|
||||||
|
lastMeasuredText = text;
|
||||||
|
lastMeasuredStyle = textStyle;
|
||||||
|
contentMeasureValid = true;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Label::draw()
|
void Label::draw()
|
||||||
{
|
{
|
||||||
if (dirty && show)
|
if (dirty && show)
|
||||||
@@ -34,16 +139,6 @@ void Label::draw()
|
|||||||
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
settextstyle(textStyle.nHeight, textStyle.nWidth, textStyle.lpszFace,
|
||||||
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
textStyle.nEscapement, textStyle.nOrientation, textStyle.nWeight,
|
||||||
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
textStyle.bItalic, textStyle.bUnderline, textStyle.bStrikeOut); //设置字体样式
|
||||||
|
|
||||||
const int newWidth = textwidth(text.c_str());
|
|
||||||
const int newHeight = textheight(text.c_str());
|
|
||||||
if (newWidth != this->width || newHeight != this->height)
|
|
||||||
{
|
|
||||||
if (hasSnap)
|
|
||||||
discardBackground();
|
|
||||||
this->width = newWidth;
|
|
||||||
this->height = newHeight;
|
|
||||||
}
|
|
||||||
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
if ((saveBkX != this->x) || (saveBkY != this->y) || (!hasSnap) || (saveWidth != this->width) || (saveHeight != this->height) || !saveBkImage)
|
||||||
saveBackground(this->x, this->y, this->width, this->height);
|
saveBackground(this->x, this->y, this->width, this->height);
|
||||||
// 恢复背景(清除旧内容)
|
// 恢复背景(清除旧内容)
|
||||||
@@ -53,12 +148,24 @@ void Label::draw()
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//用于“隐藏提示框”时调用(还原并释放快照)
|
|
||||||
void Label::hide()
|
void Label::hide()
|
||||||
{
|
{
|
||||||
discardBackground(); // 还原并释放快照
|
discardBackground(); // 还原并释放快照
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Label::setDirty(bool dirty)
|
||||||
|
{
|
||||||
|
if (dirty)
|
||||||
|
{
|
||||||
|
// 只要 Label 进入脏状态,就在绘制前把内容驱动尺寸同步到运行态。
|
||||||
|
// 这样文本变化和字体样式变化都会在 draw() 之前完成几何刷新。
|
||||||
|
refreshContentDrivenRuntimeGeometry();
|
||||||
|
}
|
||||||
|
this->dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
void Label::setTextdisap(bool key)
|
void Label::setTextdisap(bool key)
|
||||||
{
|
{
|
||||||
textBkDisap = key;
|
textBkDisap = key;
|
||||||
@@ -74,5 +181,14 @@ void Label::setTextBkColor(COLORREF color)
|
|||||||
void Label::setText(std::string text)
|
void Label::setText(std::string text)
|
||||||
{
|
{
|
||||||
this->text = text;
|
this->text = text;
|
||||||
this->dirty = true;
|
refreshContentDrivenRuntimeGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Label::applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect)
|
||||||
|
{
|
||||||
|
// 通用布局器给出的 rect.width/rect.height 只代表“外部几何解算结果”。
|
||||||
|
// Label 当前阶段的语义是内容驱动尺寸,因此这里只接位置,不接外部宽高,
|
||||||
|
// 宽高继续由 refreshContentDrivenRuntimeGeometry() 按文本和字体样式刷新。
|
||||||
|
refreshContentDrivenRuntimeGeometry();
|
||||||
|
applyRuntimeRectDirect(rect.worldX, rect.worldY, this->width, this->height);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,23 +24,47 @@ class Label : public Control
|
|||||||
std::string text; //标签文本
|
std::string text; //标签文本
|
||||||
COLORREF textBkColor; //标签背景颜色
|
COLORREF textBkColor; //标签背景颜色
|
||||||
bool textBkDisap = false; //标签背景是否透明
|
bool textBkDisap = false; //标签背景是否透明
|
||||||
|
std::string lastMeasuredText; // 最近一次参与尺寸测量的文本
|
||||||
|
StellarX::ControlText lastMeasuredStyle; // 最近一次参与尺寸测量的字体样式
|
||||||
|
bool contentMeasureValid = false; // 当前内容尺寸缓存是否有效
|
||||||
|
|
||||||
//标签事件处理(标签无事件)不实现具体代码
|
//标签事件处理(标签无事件)不实现具体代码
|
||||||
bool handleEvent(const ExMessage& msg) override { return false; }
|
bool handleEvent(const ExMessage& msg) override { return false; }
|
||||||
//用来检查对话框是否模态,此控件不做实现
|
//用来检查对话框是否模态,此控件不做实现
|
||||||
bool model() const override { return false; };
|
bool model() const override { return false; };
|
||||||
|
// 根据当前文本和字体样式刷新运行态尺寸。
|
||||||
|
// 这是 Label 的内容驱动几何入口:
|
||||||
|
// - 覆盖文本变化
|
||||||
|
// - 覆盖字体样式变化
|
||||||
|
// - 只更新运行态宽高,不自动提交设计基线 local*
|
||||||
|
void refreshContentDrivenRuntimeGeometry();
|
||||||
public:
|
public:
|
||||||
|
// 标签字体样式保持公开,便于用户直接改字段。
|
||||||
|
// 但直接修改 textStyle 后,必须显式调用 setDirty(true),
|
||||||
|
// 才会触发内容驱动尺寸刷新并让新的运行态宽高生效。
|
||||||
StellarX::ControlText textStyle; //标签文本样式
|
StellarX::ControlText textStyle; //标签文本样式
|
||||||
public:
|
public:
|
||||||
|
// 默认构造:创建一个内容驱动尺寸的标签
|
||||||
Label();
|
Label();
|
||||||
|
// 指定位置、文本和基础样式构造标签
|
||||||
Label(int x, int y, std::string text = "标签", COLORREF textcolor = BLACK, COLORREF bkColor = RGB(255, 255, 255));
|
Label(int x, int y, std::string text = "标签", COLORREF textcolor = BLACK, COLORREF bkColor = RGB(255, 255, 255));
|
||||||
|
|
||||||
|
// 绘制标签文本
|
||||||
void draw() override;
|
void draw() override;
|
||||||
|
//用于“隐藏提示框”时调用(还原并释放快照)
|
||||||
void hide();
|
void hide();
|
||||||
//设置标签背景是否透明
|
// Label 的内容尺寸由文本和字体样式共同决定。
|
||||||
|
// 因此只要进入 dirty 状态,就在绘制前先刷新一次运行态尺寸,
|
||||||
|
// 避免 draw() 阶段继续承担新的几何决策。
|
||||||
|
void setDirty(bool dirty) override;
|
||||||
|
// 设置标签背景是否透明
|
||||||
void setTextdisap(bool key);
|
void setTextdisap(bool key);
|
||||||
//设置标签背景颜色
|
// 设置标签背景颜色
|
||||||
void setTextBkColor(COLORREF color);
|
void setTextBkColor(COLORREF color);
|
||||||
//设置标签文本
|
// 设置标签文本,并触发内容驱动尺寸刷新
|
||||||
void setText(std::string text);
|
void setText(std::string text);
|
||||||
|
protected:
|
||||||
|
// 统一布局器只负责安置 Label 的运行态位置;
|
||||||
|
// 宽高仍由 Label 自己的内容驱动路径决定。
|
||||||
|
void applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect) override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ namespace StellarX
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// 模态:阻塞直到关闭,返回结果
|
// 模态:阻塞直到关闭,返回结果
|
||||||
|
// 直接创建并显示模态消息框,直到用户关闭后返回结果
|
||||||
static MessageBoxResult showModal(Window& wnd,
|
static MessageBoxResult showModal(Window& wnd,
|
||||||
const std::string& text,
|
const std::string& text,
|
||||||
const std::string& caption = "提示",
|
const std::string& caption = "提示",
|
||||||
MessageBoxType type = MessageBoxType::OK);
|
MessageBoxType type = MessageBoxType::OK);
|
||||||
|
|
||||||
// 非模态:立即返回,通过回调异步获取结果
|
// 非模态:立即返回,通过回调异步获取结果
|
||||||
|
// 创建并显示非模态消息框,关闭时通过回调返回结果
|
||||||
static void showAsync(Window& wnd,
|
static void showAsync(Window& wnd,
|
||||||
const std::string& text,
|
const std::string& text,
|
||||||
const std::string& caption = "提示",
|
const std::string& caption = "提示",
|
||||||
|
|||||||
+219
-64
@@ -1,6 +1,25 @@
|
|||||||
#include "TabControl.h"
|
#include "TabControl.h"
|
||||||
#include "SxLog.h"
|
#include "SxLog.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool SxTabRectsIntersect(const RECT& a, const RECT& b)
|
||||||
|
{
|
||||||
|
return a.left < b.right && a.right > b.left &&
|
||||||
|
a.top < b.bottom && a.bottom > b.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT SxTabUnionRect(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
inline void TabControl::initTabBar()
|
inline void TabControl::initTabBar()
|
||||||
{
|
{
|
||||||
if (controls.empty())return;
|
if (controls.empty())return;
|
||||||
@@ -65,9 +84,9 @@ inline void TabControl::initTabPage()
|
|||||||
// TabControl 内部页签页仍然保留专用布局:
|
// TabControl 内部页签页仍然保留专用布局:
|
||||||
// 这里负责把“当前选项卡容器矩形”拆分成页签栏和页面区,
|
// 这里负责把“当前选项卡容器矩形”拆分成页签栏和页面区,
|
||||||
// 不把这部分细节下放到通用布局解算器里。
|
// 不把这部分细节下放到通用布局解算器里。
|
||||||
//子控件坐标原点
|
// 但页面内容子树的世界坐标映射职责,继续归页面 Canvas 自己管理。
|
||||||
int nX = 0;
|
// 因此这里不再手工遍历页内子控件做 setX/setY,
|
||||||
int nY = 0;
|
// 只安置每个页面 Canvas 的矩形,让 Canvas 通过自身 relayoutManagedChildren() 收口。
|
||||||
switch (this->tabPlacement)
|
switch (this->tabPlacement)
|
||||||
{
|
{
|
||||||
case StellarX::TabPlacement::Top:
|
case StellarX::TabPlacement::Top:
|
||||||
@@ -78,16 +97,6 @@ inline void TabControl::initTabPage()
|
|||||||
c.second->setWidth(this->width);
|
c.second->setWidth(this->width);
|
||||||
c.second->setHeight(this->height - tabBarHeight);
|
c.second->setHeight(this->height - tabBarHeight);
|
||||||
}
|
}
|
||||||
nX = this->x;
|
|
||||||
nY = this->y + tabBarHeight;
|
|
||||||
for (auto& c : controls)
|
|
||||||
{
|
|
||||||
for (auto& v : c.second->getControls())
|
|
||||||
{
|
|
||||||
v->setX(v->getLocalX() + nX);
|
|
||||||
v->setY(v->getLocalY() + nY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StellarX::TabPlacement::Bottom:
|
case StellarX::TabPlacement::Bottom:
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
@@ -97,16 +106,6 @@ inline void TabControl::initTabPage()
|
|||||||
c.second->setWidth(this->width);
|
c.second->setWidth(this->width);
|
||||||
c.second->setHeight(this->height - tabBarHeight);
|
c.second->setHeight(this->height - tabBarHeight);
|
||||||
}
|
}
|
||||||
nX = this->x;
|
|
||||||
nY = this->y;
|
|
||||||
for (auto& c : controls)
|
|
||||||
{
|
|
||||||
for (auto& v : c.second->getControls())
|
|
||||||
{
|
|
||||||
v->setX(v->getLocalX() + nX);
|
|
||||||
v->setY(v->getLocalY() + nY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StellarX::TabPlacement::Left:
|
case StellarX::TabPlacement::Left:
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
@@ -116,16 +115,6 @@ inline void TabControl::initTabPage()
|
|||||||
c.second->setWidth(this->width - tabBarHeight);
|
c.second->setWidth(this->width - tabBarHeight);
|
||||||
c.second->setHeight(this->height);
|
c.second->setHeight(this->height);
|
||||||
}
|
}
|
||||||
nX = this->x + tabBarHeight;
|
|
||||||
nY = this->y;
|
|
||||||
for (auto& c : controls)
|
|
||||||
{
|
|
||||||
for (auto& v : c.second->getControls())
|
|
||||||
{
|
|
||||||
v->setX(v->getLocalX() + nX);
|
|
||||||
v->setY(v->getLocalY() + nY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case StellarX::TabPlacement::Right:
|
case StellarX::TabPlacement::Right:
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
@@ -135,16 +124,6 @@ inline void TabControl::initTabPage()
|
|||||||
c.second->setWidth(this->width - tabBarHeight);
|
c.second->setWidth(this->width - tabBarHeight);
|
||||||
c.second->setHeight(this->height);
|
c.second->setHeight(this->height);
|
||||||
}
|
}
|
||||||
nX = this->x;
|
|
||||||
nY = this->y;
|
|
||||||
for (auto& c : controls)
|
|
||||||
{
|
|
||||||
for (auto& v : c.second->getControls())
|
|
||||||
{
|
|
||||||
v->setX(v->getLocalX() + nX);
|
|
||||||
v->setY(v->getLocalY() + nY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -163,12 +142,18 @@ void TabControl::refreshRuntimeLayout()
|
|||||||
TabControl::TabControl() :Canvas()
|
TabControl::TabControl() :Canvas()
|
||||||
{
|
{
|
||||||
this->id = "TabControl";
|
this->id = "TabControl";
|
||||||
|
// TabControl 作为外层容器,当前阶段显式允许双轴 Stretch;
|
||||||
|
// 内部页签栏和页面区仍由自己的专用布局逻辑管理。
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
|
this->layoutCapability.allowStretchY = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TabControl::TabControl(int x, int y, int width, int height)
|
TabControl::TabControl(int x, int y, int width, int height)
|
||||||
: Canvas(x, y, width, height)
|
: Canvas(x, y, width, height)
|
||||||
{
|
{
|
||||||
this->id = "TabControl";
|
this->id = "TabControl";
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
|
this->layoutCapability.allowStretchY = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TabControl::~TabControl()
|
TabControl::~TabControl()
|
||||||
@@ -218,13 +203,13 @@ void TabControl::draw()
|
|||||||
Canvas::draw();
|
Canvas::draw();
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.first->setDirty(true);
|
c.second->setDirty(true);
|
||||||
c.first->draw();
|
c.second->draw();
|
||||||
}
|
}
|
||||||
for (auto& c : controls)
|
for (auto& c : controls)
|
||||||
{
|
{
|
||||||
c.second->setDirty(true);
|
c.first->setDirty(true);
|
||||||
c.second->draw();
|
c.first->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首次绘制时处理默认激活页签
|
// 首次绘制时处理默认激活页签
|
||||||
@@ -242,28 +227,90 @@ void TabControl::draw()
|
|||||||
bool TabControl::handleEvent(const ExMessage& msg)
|
bool TabControl::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
if (!show)return false;
|
if (!show)return false;
|
||||||
|
resetEventVisualChanged();
|
||||||
bool consume = false;
|
bool consume = false;
|
||||||
for (auto& c : controls)
|
bool anyVisualChanged = false;
|
||||||
if (c.first->handleEvent(msg))
|
|
||||||
{
|
// TabControl 的同层页签按钮/页面区事件分发顺序,
|
||||||
consume = true;
|
// 与 Window / Canvas 保持一致:按倒序处理,后加入者视为更靠上层。
|
||||||
break;
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
}
|
|
||||||
if (!consume)
|
|
||||||
{
|
{
|
||||||
for (auto& c : controls)
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
if (c.second->IsVisible())
|
{
|
||||||
if (c.second->handleEvent(msg))
|
if (!consume)
|
||||||
|
{
|
||||||
|
if (it->first->handleEvent(msg))
|
||||||
{
|
{
|
||||||
consume = true;
|
consume = true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (it->first->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
else if (it->first->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (!it->second->IsVisible())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!consume)
|
||||||
|
{
|
||||||
|
if (it->second->handleEvent(msg))
|
||||||
|
{
|
||||||
|
consume = true;
|
||||||
|
}
|
||||||
|
if (it->second->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
else if (it->second->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
if (it->first->handleEvent(msg))
|
||||||
|
{
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!consume)
|
||||||
|
{
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
if (it->second->IsVisible())
|
||||||
|
if (it->second->handleEvent(msg))
|
||||||
|
{
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dirty)
|
if (dirty)
|
||||||
requestRepaint(parent);
|
requestRepaint(parent);
|
||||||
|
markEventVisualChanged(anyVisualChanged || dirty);
|
||||||
return consume;
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TabControl::clearTransientMouseState()
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->first->IsVisible() && it->first->clearTransientMouseState())
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
if (it->second->IsVisible() && it->second->clearTransientMouseState())
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control)
|
void TabControl::add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control)
|
||||||
{
|
{
|
||||||
controls.push_back(std::move(control));
|
controls.push_back(std::move(control));
|
||||||
@@ -399,8 +446,15 @@ void TabControl::setActiveIndex(int idx)
|
|||||||
defaultActivation = idx;
|
defaultActivation = idx;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 外部重复激活“已经处于激活状态”的页签时,不应再把整条 onToggleOn 链重跑一遍。
|
||||||
|
// 否则当前可见页面会重复执行 onWindowResize()/setIsVisible(true),
|
||||||
|
// 对页内像 Table 这种会越出页面边界绘制的控件,容易把快照链再次扰乱,留下残影。
|
||||||
if (idx >= 0 && idx < controls.size())
|
if (idx >= 0 && idx < controls.size())
|
||||||
|
{
|
||||||
|
if (getActiveIndex() == idx)
|
||||||
|
return;
|
||||||
controls[idx].first->setButtonClick(true);
|
controls[idx].first->setButtonClick(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,17 +498,118 @@ void TabControl::requestRepaint(Control* parent)
|
|||||||
|
|
||||||
if (this == parent)
|
if (this == parent)
|
||||||
{
|
{
|
||||||
|
RECT coverage{};
|
||||||
|
bool hasCoverage = false;
|
||||||
|
auto commitTabUnit = [&](Control* unit, bool forceOverlayRedraw)
|
||||||
|
{
|
||||||
|
if (!unit || !unit->IsVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool directDirty = unit->isDirty();
|
||||||
|
const bool subtreeDirty = unit->hasManagedDirtySubtree();
|
||||||
|
|
||||||
|
if (forceOverlayRedraw)
|
||||||
|
{
|
||||||
|
// 下层单元已经写过像素,上层页签/页面作为 overlay 补画时,
|
||||||
|
// 必须先丢掉旧快照,重新抓取当前背景后再画,否则会把旧背景再贴回来。
|
||||||
|
unit->invalidateBackgroundSnapshot();
|
||||||
|
unit->setDirty(true);
|
||||||
|
unit->draw();
|
||||||
|
}
|
||||||
|
else if (directDirty)
|
||||||
|
{
|
||||||
|
unit->draw();
|
||||||
|
}
|
||||||
|
else if (subtreeDirty)
|
||||||
|
{
|
||||||
|
unit->commitManagedRepaint();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECT rc = unit->getManagedRepaintCoverageRect();
|
||||||
|
if (!hasCoverage)
|
||||||
|
{
|
||||||
|
coverage = rc;
|
||||||
|
hasCoverage = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coverage = SxTabUnionRect(coverage, rc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 局部重绘必须和 draw() 维持同一套顺序:
|
||||||
|
// 先页面,再页签按钮。
|
||||||
|
// 否则页签按钮上的 Tooltip 会被后画的页面盖掉,
|
||||||
|
// 表现为“有页面打开时 Tooltip 看不到,所有页关闭时才正常”。
|
||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
{
|
{
|
||||||
if (control.first->isDirty() && control.first->IsVisible())
|
Control* page = control.second.get();
|
||||||
control.first->draw();
|
if (!page->IsVisible())
|
||||||
if (control.second->isDirty() && control.second->IsVisible())
|
continue;
|
||||||
control.second->draw();
|
|
||||||
|
if (page->hasManagedDirtySubtree())
|
||||||
|
{
|
||||||
|
commitTabUnit(page, false);
|
||||||
|
}
|
||||||
|
else if (hasCoverage && SxTabRectsIntersect(page->getManagedRepaintCoverageRect(), coverage))
|
||||||
|
{
|
||||||
|
commitTabUnit(page, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& control : controls)
|
||||||
|
{
|
||||||
|
Control* button = control.first.get();
|
||||||
|
if (!button->IsVisible())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (button->hasManagedDirtySubtree())
|
||||||
|
{
|
||||||
|
commitTabUnit(button, false);
|
||||||
|
}
|
||||||
|
else if (hasCoverage && SxTabRectsIntersect(button->getManagedRepaintCoverageRect(), coverage))
|
||||||
|
{
|
||||||
|
commitTabUnit(button, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
onRequestRepaintAsRoot();
|
||||||
onRequestRepaintAsRoot();
|
}
|
||||||
|
|
||||||
|
bool TabControl::hasManagedDirtySubtree() const
|
||||||
|
{
|
||||||
|
if (dirty)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (const auto& control : controls)
|
||||||
|
{
|
||||||
|
if (control.first->IsVisible() && control.first->hasManagedDirtySubtree())
|
||||||
|
return true;
|
||||||
|
if (control.second->IsVisible() && control.second->hasManagedDirtySubtree())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT TabControl::getManagedRepaintCoverageRect() const
|
||||||
|
{
|
||||||
|
// TabControl 的 draw() 会写自身背景、全部页签按钮以及全部页面。
|
||||||
|
// 因此 coverage 也必须按“页签按钮 + 页面”递归并集,避免内部 Tooltip 等附加绘制区域被漏算。
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
for (const auto& control : controls)
|
||||||
|
{
|
||||||
|
if (control.first->IsVisible())
|
||||||
|
coverage = SxTabUnionRect(coverage, control.first->getManagedRepaintCoverageRect());
|
||||||
|
if (control.second->IsVisible())
|
||||||
|
coverage = SxTabUnionRect(coverage, control.second->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
return coverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TabControl::canCommitManagedPartialRepaint() const
|
bool TabControl::canCommitManagedPartialRepaint() const
|
||||||
|
|||||||
+11
-5
@@ -42,7 +42,9 @@ private:
|
|||||||
// 统一刷新 TabControl 当前运行态下的页签栏和页面区布局。
|
// 统一刷新 TabControl 当前运行态下的页签栏和页面区布局。
|
||||||
void refreshRuntimeLayout();
|
void refreshRuntimeLayout();
|
||||||
public:
|
public:
|
||||||
|
// 默认构造:创建一个空 TabControl
|
||||||
TabControl();
|
TabControl();
|
||||||
|
// 指定初始位置和尺寸构造 TabControl
|
||||||
TabControl(int x, int y, int width, int height);
|
TabControl(int x, int y, int width, int height);
|
||||||
~TabControl();
|
~TabControl();
|
||||||
|
|
||||||
@@ -54,10 +56,12 @@ public:
|
|||||||
|
|
||||||
void draw() override;
|
void draw() override;
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
// 只转发清理页签按钮和当前可见页面中的鼠标临时状态,不升级为整块 TabControl 重绘。
|
||||||
|
bool clearTransientMouseState() override;
|
||||||
|
|
||||||
//添加页签+页
|
// 添加一个“页签按钮 + 页面 Canvas”组合
|
||||||
void add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control);
|
void add(std::pair<std::unique_ptr<Button>, std::unique_ptr<Canvas>>&& control);
|
||||||
//添加为某个页添加控件
|
// 按页签文本把控件添加到对应页面
|
||||||
void add(std::string tabText, std::unique_ptr<Control> control);
|
void add(std::string tabText, std::unique_ptr<Control> control);
|
||||||
//设置页签位置
|
//设置页签位置
|
||||||
void setTabPlacement(StellarX::TabPlacement placement);
|
void setTabPlacement(StellarX::TabPlacement placement);
|
||||||
@@ -68,16 +72,18 @@ public:
|
|||||||
void onWindowResize() override;
|
void onWindowResize() override;
|
||||||
//获取当前激活页签索引
|
//获取当前激活页签索引
|
||||||
int getActiveIndex() const;
|
int getActiveIndex() const;
|
||||||
//设置当前激活页签索引
|
// 设置当前激活页签索引;若已是当前激活页则直接返回
|
||||||
void setActiveIndex(int idx);
|
void setActiveIndex(int idx);
|
||||||
//获取页签数量
|
// 获取页签数量
|
||||||
int count() const;
|
int count() const;
|
||||||
//通过页签文本返回索引
|
// 通过页签文本查找索引
|
||||||
int indexOf(const std::string& tabText) const;
|
int indexOf(const std::string& tabText) const;
|
||||||
//设置脏区并请求重绘
|
//设置脏区并请求重绘
|
||||||
void setDirty(bool dirty) override;
|
void setDirty(bool dirty) override;
|
||||||
//请求父控件重绘
|
//请求父控件重绘
|
||||||
void requestRepaint(Control* parent)override; // 托管模式下登记为 root;非托管模式下局部更新脏按钮/脏页面
|
void requestRepaint(Control* parent)override; // 托管模式下登记为 root;非托管模式下局部更新脏按钮/脏页面
|
||||||
|
bool hasManagedDirtySubtree() const override;
|
||||||
|
RECT getManagedRepaintCoverageRect() const override;
|
||||||
bool canCommitManagedPartialRepaint() const override; // 判断当前 TabControl 是否可安全做局部提交
|
bool canCommitManagedPartialRepaint() const override; // 判断当前 TabControl 是否可安全做局部提交
|
||||||
void commitManagedRepaint() override; // 托管收口阶段执行 TabControl 的真正重绘
|
void commitManagedRepaint() override; // 托管收口阶段执行 TabControl 的真正重绘
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -152,9 +152,18 @@ void Table::initTextWaH()
|
|||||||
// 最终表宽/高:内容 + 对称边框
|
// 最终表宽/高:内容 + 对称边框
|
||||||
this->width = contentW + (border << 1);
|
this->width = contentW + (border << 1);
|
||||||
this->height = headerH + rowsH + footerH + (border << 1);
|
this->height = headerH + rowsH + footerH + (border << 1);
|
||||||
// 记录原始宽高用于锚点布局的参考;此处仅在初始化单元尺寸时重置
|
// 这是 Table 自身受控的“结构尺寸 -> 设计基线”刷新点:
|
||||||
|
// 表格总宽高本来就由表头、表体、页脚和分页按钮共同决定,
|
||||||
|
// 因此这里允许同步 localWidth/localHeight,作为后续锚点解算参考。
|
||||||
|
const bool baselineChanged = (this->localWidth != this->width) || (this->localHeight != this->height);
|
||||||
this->localWidth = this->width;
|
this->localWidth = this->width;
|
||||||
this->localHeight = this->height;
|
this->localHeight = this->height;
|
||||||
|
if (baselineChanged)
|
||||||
|
{
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("Table 刷新结构设计基线:id=", "Table refresh structural design rect: id=") << id
|
||||||
|
<< SX_T(" size=(", " size=(") << this->width << "," << this->height << ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Table::initButton()
|
void Table::initButton()
|
||||||
@@ -285,6 +294,10 @@ void Table::drawPageNum()
|
|||||||
pageNum->textStyle = this->textStyle;
|
pageNum->textStyle = this->textStyle;
|
||||||
if (StellarX::FillMode::Null == tableFillMode)
|
if (StellarX::FillMode::Null == tableFillMode)
|
||||||
pageNum->setTextdisap(true);
|
pageNum->setTextdisap(true);
|
||||||
|
// Table 每次整表重绘前都会先回贴自己的背景快照,
|
||||||
|
// 页码区域会被一起清空。即便页码文本本身没变化,也必须强制把 Label 重新设脏,
|
||||||
|
// 否则 Label::draw() 会因为 dirty=false 直接跳过,导致页码在表格重绘后消失。
|
||||||
|
pageNum->setDirty(true);
|
||||||
pageNum->draw();
|
pageNum->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +379,7 @@ Table::Table(int x, int y)
|
|||||||
this->id = "Table";
|
this->id = "Table";
|
||||||
// Table 当前正式能力边界:
|
// Table 当前正式能力边界:
|
||||||
// 仅允许 X 轴 Stretch,Y 轴固定尺寸。
|
// 仅允许 X 轴 Stretch,Y 轴固定尺寸。
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
this->layoutCapability.allowStretchY = false;
|
this->layoutCapability.allowStretchY = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +389,8 @@ void Table::applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect)
|
|||||||
{
|
{
|
||||||
// Table 不能像普通控件那样直接写入 width/height:
|
// Table 不能像普通控件那样直接写入 width/height:
|
||||||
// 它的 setWidth() 内部会联动列宽与页脚布局,因此这里必须复用原有 setter 副作用。
|
// 它的 setWidth() 内部会联动列宽与页脚布局,因此这里必须复用原有 setter 副作用。
|
||||||
// 同时由于 setHeight() 为空实现,Y 轴会自然保持固定尺寸。
|
// 同时 Table 的 Y Fixed 是当前版本的正式行为边界:
|
||||||
|
// 即便统一解算器给出了新的 rect.height,这里也不会接收该高度。
|
||||||
if (this->x != rect.worldX)
|
if (this->x != rect.worldX)
|
||||||
setX(rect.worldX);
|
setX(rect.worldX);
|
||||||
if (this->y != rect.worldY)
|
if (this->y != rect.worldY)
|
||||||
@@ -383,7 +398,13 @@ void Table::applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect)
|
|||||||
if (this->width != rect.width)
|
if (this->width != rect.width)
|
||||||
setWidth(rect.width);
|
setWidth(rect.width);
|
||||||
if (this->height != rect.height)
|
if (this->height != rect.height)
|
||||||
setHeight(rect.height);
|
{
|
||||||
|
SX_LOGD("Layout")
|
||||||
|
<< SX_T("Table 忽略 Y 轴运行态高度写入:id=", "Table ignore runtime Y resize: id=")
|
||||||
|
<< id
|
||||||
|
<< SX_T(" currentH=", " currentH=") << this->height
|
||||||
|
<< SX_T(" requestedH=", " requestedH=") << rect.height;
|
||||||
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,20 +503,117 @@ void Table::draw()
|
|||||||
bool Table::handleEvent(const ExMessage& msg)
|
bool Table::handleEvent(const ExMessage& msg)
|
||||||
{
|
{
|
||||||
if (!show)return false;
|
if (!show)return false;
|
||||||
|
resetEventVisualChanged();
|
||||||
bool consume = false;
|
bool consume = false;
|
||||||
|
bool anyVisualChanged = false;
|
||||||
if (!this->isShowPageButton)
|
if (!this->isShowPageButton)
|
||||||
return consume;
|
return consume;
|
||||||
|
|
||||||
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
|
{
|
||||||
|
// Table 内部分页按钮也按“后绘制者优先”的倒序语义处理。
|
||||||
|
// drawButton() 里是 prev 再 next,因此这里先 next 再 prev。
|
||||||
|
if (nextButton)
|
||||||
|
{
|
||||||
|
if (!consume)
|
||||||
|
{
|
||||||
|
consume = nextButton->handleEvent(msg);
|
||||||
|
if (nextButton->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
else if (nextButton->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prevButton)
|
||||||
|
{
|
||||||
|
if (!consume)
|
||||||
|
{
|
||||||
|
consume = prevButton->handleEvent(msg);
|
||||||
|
if (prevButton->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
else if (prevButton->clearTransientMouseState())
|
||||||
|
{
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (prevButton)consume = prevButton->handleEvent(msg);
|
// 点击类消息仍保持“后绘制者优先”的倒序命中语义。
|
||||||
if (nextButton && !consume)
|
// 但和 WM_MOUSEMOVE 一样,分页按钮的视觉状态变化必须向上提升为 Table 重绘,
|
||||||
|
// 否则按钮内部已经变成 hover/click,父级却不知道需要重画整张 Table,
|
||||||
|
// 就会出现“状态实际变了,但屏幕上直到下一次翻页才看见”的现象。
|
||||||
|
if (nextButton)
|
||||||
|
{
|
||||||
consume = nextButton->handleEvent(msg);
|
consume = nextButton->handleEvent(msg);
|
||||||
|
if (nextButton->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
|
if (prevButton && !consume)
|
||||||
|
{
|
||||||
|
consume = prevButton->handleEvent(msg);
|
||||||
|
if (prevButton->didEventAffectVisual())
|
||||||
|
anyVisualChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Table 当前还没有像 TabControl 那样把分页按钮做成可独立提交的内部绘制单元。
|
||||||
|
// 因此只要内部按钮的 hover / click / leave 造成视觉变化,就必须把整张 Table 设脏,
|
||||||
|
// 让父容器通过现有的 Table::draw() 链把页脚区域正确重画出来。
|
||||||
|
if (anyVisualChanged)
|
||||||
|
dirty = true;
|
||||||
|
|
||||||
if (dirty)
|
if (dirty)
|
||||||
requestRepaint(parent);
|
requestRepaint(parent);
|
||||||
|
markEventVisualChanged(anyVisualChanged || dirty);
|
||||||
return consume;
|
return consume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Table::clearTransientMouseState()
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
if (nextButton && nextButton->clearTransientMouseState())
|
||||||
|
changed = true;
|
||||||
|
if (prevButton && prevButton->clearTransientMouseState())
|
||||||
|
changed = true;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT Table::getManagedRepaintCoverageRect() const
|
||||||
|
{
|
||||||
|
RECT coverage = getBoundsRect();
|
||||||
|
|
||||||
|
if (pageNum && pageNum->IsVisible())
|
||||||
|
{
|
||||||
|
const RECT pageRect = pageNum->getManagedRepaintCoverageRect();
|
||||||
|
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->getManagedRepaintCoverageRect();
|
||||||
|
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->getManagedRepaintCoverageRect();
|
||||||
|
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();
|
||||||
|
|||||||
@@ -101,11 +101,16 @@ private:
|
|||||||
bool model() const override { return false; };
|
bool model() const override { return false; };
|
||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; // 文本样式
|
StellarX::ControlText textStyle; // 文本样式
|
||||||
|
// 修改表格运行态 X,并同步内部页脚元素位置
|
||||||
void setX(int x) override;
|
void setX(int x) override;
|
||||||
|
// 修改表格运行态 Y,并同步内部页脚元素位置
|
||||||
void setY(int y) override;
|
void setY(int y) override;
|
||||||
|
// 修改表格运行态宽度;当前支持横向拉伸
|
||||||
void setWidth(int width) override;
|
void setWidth(int width) override;
|
||||||
|
// 修改表格运行态高度;当前版本作为行为边界明确忽略纵向 Stretch
|
||||||
void setHeight(int height) override;
|
void setHeight(int height) override;
|
||||||
public:
|
public:
|
||||||
|
// 构造一个基础表格,后续通过表头和数据初始化内容
|
||||||
Table(int x, int y);
|
Table(int x, int y);
|
||||||
~Table();
|
~Table();
|
||||||
|
|
||||||
@@ -113,11 +118,16 @@ public:
|
|||||||
void draw() override;
|
void draw() override;
|
||||||
//事件处理
|
//事件处理
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
|
// 只清理分页按钮的 hover / tooltip / 临时按下态,不触发表格整体重绘。
|
||||||
|
bool clearTransientMouseState() override;
|
||||||
|
// Table 重绘时会一并绘制页码 Label 和分页按钮,coverage 需要把这些内部绘制单元并入。
|
||||||
|
RECT getManagedRepaintCoverageRect() 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);
|
||||||
|
// 设置表格数据(多行覆盖)
|
||||||
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);
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ TextBox::TextBox(int x, int y, int width, int height, std::string text, StellarX
|
|||||||
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
:Control(x, y, width, height), text(text), mode(mode), shape(shape)
|
||||||
{
|
{
|
||||||
this->id = "TextBox";
|
this->id = "TextBox";
|
||||||
|
// TextBox 当前阶段的默认布局策略:
|
||||||
|
// - 水平方向允许通过锚点语义拉伸
|
||||||
|
// - 垂直方向当前实现保持固定尺寸,不在本轮引入随高度变化的视觉自适应
|
||||||
|
this->layoutCapability.allowStretchX = true;
|
||||||
|
this->layoutCapability.allowStretchY = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::draw()
|
void TextBox::draw()
|
||||||
|
|||||||
@@ -31,23 +31,26 @@ class TextBox : public Control
|
|||||||
public:
|
public:
|
||||||
StellarX::ControlText textStyle; //文本样式
|
StellarX::ControlText textStyle; //文本样式
|
||||||
|
|
||||||
|
// 构造文本框;支持输入模式和只读模式
|
||||||
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
TextBox(int x, int y, int width, int height, std::string text = "", StellarX::TextBoxmode mode = StellarX::TextBoxmode::INPUT_MODE, StellarX::ControlShape shape = StellarX::ControlShape::RECTANGLE);
|
||||||
|
// 绘制文本框
|
||||||
void draw() override;
|
void draw() override;
|
||||||
|
// 处理输入框鼠标交互和文本录入
|
||||||
bool handleEvent(const ExMessage& msg) override;
|
bool handleEvent(const ExMessage& msg) override;
|
||||||
//设置模式
|
// 设置文本框模式
|
||||||
void setMode(StellarX::TextBoxmode mode);
|
void setMode(StellarX::TextBoxmode mode);
|
||||||
//设置可输入最大字符长度
|
// 设置可输入最大字符长度
|
||||||
void setMaxCharLen(size_t len);
|
void setMaxCharLen(size_t len);
|
||||||
//设置形状
|
// 设置文本框形状
|
||||||
void setTextBoxshape(StellarX::ControlShape shape);
|
void setTextBoxshape(StellarX::ControlShape shape);
|
||||||
//设置边框颜色
|
// 设置边框颜色
|
||||||
void setTextBoxBorder(COLORREF color);
|
void setTextBoxBorder(COLORREF color);
|
||||||
//设置背景颜色
|
// 设置背景颜色
|
||||||
void setTextBoxBk(COLORREF color);
|
void setTextBoxBk(COLORREF color);
|
||||||
//设置文本
|
// 设置当前文本
|
||||||
void setText(std::string text);
|
void setText(std::string text);
|
||||||
|
|
||||||
//获取文本
|
// 获取当前文本
|
||||||
std::string getText() const;
|
std::string getText() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+178
-21
@@ -33,6 +33,9 @@ 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 void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
|
||||||
|
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays);
|
||||||
|
|
||||||
bool Window::isManagedDispatchActive() const
|
bool Window::isManagedDispatchActive() const
|
||||||
{
|
{
|
||||||
return managedDispatchActive;
|
return managedDispatchActive;
|
||||||
@@ -56,9 +59,20 @@ void Window::requestManagedRepaint(Control* source)
|
|||||||
if (!root)
|
if (!root)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RECT coverage = root->getBoundsRect();
|
RECT coverage = root->getManagedRepaintCoverageRect();
|
||||||
if (root->canCommitManagedPartialRepaint())
|
if (root->canCommitManagedPartialRepaint())
|
||||||
coverage = source->getBoundsRect();
|
{
|
||||||
|
// 对支持局部提交的 root,coverage 不能再盯着最深处的 source;
|
||||||
|
// 否则像“三层 Canvas 里的按钮变色”这种情况,只会登记成一个很小的叶子矩形,
|
||||||
|
// 顶层 root 提交时既容易漏掉那条直接脏分支,也会低估后续 overlay 补画范围。
|
||||||
|
Control* branch = source->getManagedRepaintDirectBranch(root);
|
||||||
|
coverage = branch ? branch->getManagedRepaintCoverageRect() : source->getManagedRepaintCoverageRect();
|
||||||
|
const RECT sourceCoverage = source->getManagedRepaintCoverageRect();
|
||||||
|
coverage.left = (std::min)(coverage.left, sourceCoverage.left);
|
||||||
|
coverage.top = (std::min)(coverage.top, sourceCoverage.top);
|
||||||
|
coverage.right = (std::max)(coverage.right, sourceCoverage.right);
|
||||||
|
coverage.bottom = (std::max)(coverage.bottom, sourceCoverage.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& item : managedRepaintItems)
|
for (auto& item : managedRepaintItems)
|
||||||
{
|
{
|
||||||
@@ -138,29 +152,108 @@ void Window::flushManagedRepaint()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
BeginBatchDraw();
|
BeginBatchDraw();
|
||||||
std::vector<Control*> overlayDialogs;
|
auto unionCoverage = [](RECT& lhs, const RECT& rhs)
|
||||||
|
{
|
||||||
|
lhs.left = (std::min)(lhs.left, rhs.left);
|
||||||
|
lhs.top = (std::min)(lhs.top, rhs.top);
|
||||||
|
lhs.right = (std::max)(lhs.right, rhs.right);
|
||||||
|
lhs.bottom = (std::max)(lhs.bottom, rhs.bottom);
|
||||||
|
};
|
||||||
|
|
||||||
for (auto& item : managedRepaintItems)
|
auto redrawOverlayUnit = [](Control* unit)
|
||||||
collectManagedDialogOverlays(item.root, item.coverage, overlayDialogs);
|
{
|
||||||
|
if (!unit || !unit->IsVisible())
|
||||||
|
return;
|
||||||
|
// overlay 补画不是“沿用旧快照再贴回去”,而是重新站到当前顶层场景上合成一遍。
|
||||||
|
unit->invalidateBackgroundSnapshot();
|
||||||
|
unit->setDirty(true);
|
||||||
|
unit->draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto processManagedRoot = [&](Control* root, const RECT& initialCoverage)
|
||||||
|
{
|
||||||
|
if (!root || !root->IsVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
root->commitManagedRepaint();
|
||||||
|
RECT workingCoverage = initialCoverage;
|
||||||
|
|
||||||
|
size_t controlStartIdx = controls.size();
|
||||||
|
for (size_t i = 0; i < controls.size(); ++i)
|
||||||
|
{
|
||||||
|
if (controls[i].get() == root)
|
||||||
|
{
|
||||||
|
controlStartIdx = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶层普通控件的 overlay 补画必须是“传递式”的:
|
||||||
|
// 如果 A 重画后把上层按钮 B 也重画出来,而 B 自己又伸进更上层的 C,
|
||||||
|
// 那么 C 也必须继续补画回来,而不能只看最初 root 的 coverage 一跳收集。
|
||||||
|
if (controlStartIdx < controls.size())
|
||||||
|
{
|
||||||
|
for (size_t i = controlStartIdx; i < controls.size(); ++i)
|
||||||
|
{
|
||||||
|
Control* current = controls[i].get();
|
||||||
|
if (!current || !current->IsVisible())
|
||||||
|
continue;
|
||||||
|
const RECT currentCoverage = current->getManagedRepaintCoverageRect();
|
||||||
|
if (!SxRectsIntersect(currentCoverage, workingCoverage))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
redrawOverlayUnit(current);
|
||||||
|
unionCoverage(workingCoverage, current->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dialogStartIdx = 0;
|
||||||
|
for (size_t i = 0; i < dialogs.size(); ++i)
|
||||||
|
{
|
||||||
|
if (dialogs[i].get() == root)
|
||||||
|
{
|
||||||
|
dialogStartIdx = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog 永远位于普通顶层控件之上,也要使用扩张后的 coverage 做传递式补画。
|
||||||
|
for (size_t i = dialogStartIdx; i < dialogs.size(); ++i)
|
||||||
|
{
|
||||||
|
Control* dialog = dialogs[i].get();
|
||||||
|
if (!dialog || !dialog->IsVisible())
|
||||||
|
continue;
|
||||||
|
const RECT dialogCoverage = dialog->getManagedRepaintCoverageRect();
|
||||||
|
if (!SxRectsIntersect(dialogCoverage, workingCoverage))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
redrawOverlayUnit(dialog);
|
||||||
|
unionCoverage(workingCoverage, dialog->getManagedRepaintCoverageRect());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (auto& control : controls)
|
for (auto& control : controls)
|
||||||
{
|
{
|
||||||
for (auto& item : managedRepaintItems)
|
for (auto& item : managedRepaintItems)
|
||||||
{
|
{
|
||||||
if (item.root == control.get() && item.root && item.root->IsVisible())
|
if (item.root == control.get())
|
||||||
{
|
{
|
||||||
item.root->commitManagedRepaint();
|
processManagedRoot(item.root, item.coverage);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& dialog : overlayDialogs)
|
for (auto& dialog : dialogs)
|
||||||
{
|
{
|
||||||
if (!dialog || !dialog->IsVisible())
|
for (auto& item : managedRepaintItems)
|
||||||
continue;
|
{
|
||||||
dialog->setDirty(true);
|
if (item.root == dialog.get())
|
||||||
dialog->draw();
|
{
|
||||||
|
processManagedRoot(item.root, item.coverage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EndBatchDraw();
|
EndBatchDraw();
|
||||||
@@ -214,6 +307,44 @@ void Window::collectManagedDialogOverlays(Control* repaintRoot, const RECT& cove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* collectManagedControlOverlays(repaintRoot, coverage, overlays)
|
||||||
|
* 作用:找出在本轮 root 提交后,需要重新补画回上层的普通顶层控件。
|
||||||
|
* 规则:
|
||||||
|
* - 只处理 Window::controls 这一层的直接兄弟;
|
||||||
|
* - 从 repaintRoot 在 controls 中的位置之后开始收集;
|
||||||
|
* - 仅收集可见且与 coverage 相交的控件。
|
||||||
|
*/
|
||||||
|
static void collectManagedControlOverlays(const std::vector<std::unique_ptr<Control>>& controls,
|
||||||
|
Control* repaintRoot, const RECT& coverage, std::vector<Control*>& overlays)
|
||||||
|
{
|
||||||
|
size_t startIdx = controls.size();
|
||||||
|
for (size_t i = 0; i < controls.size(); ++i)
|
||||||
|
{
|
||||||
|
if (controls[i].get() == repaintRoot)
|
||||||
|
{
|
||||||
|
startIdx = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIdx > controls.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = startIdx; i < controls.size(); ++i)
|
||||||
|
{
|
||||||
|
Control* control = controls[i].get();
|
||||||
|
if (!control || !control->IsVisible())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (SxRectsIntersect(control->getBoundsRect(), coverage))
|
||||||
|
{
|
||||||
|
if (std::find(overlays.begin(), overlays.end(), control) == overlays.end())
|
||||||
|
overlays.push_back(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApplyResizableStyle
|
* ApplyResizableStyle
|
||||||
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
|
* 作用:统一设置可拉伸/裁剪样式,并按开关使用 WS_EX_COMPOSITED(合成双缓冲)。
|
||||||
@@ -621,19 +752,45 @@ int Window::runEventLoop()
|
|||||||
}
|
}
|
||||||
if (!consume)
|
if (!consume)
|
||||||
{
|
{
|
||||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
Control* firstConsumer = nullptr;
|
||||||
|
if (msg.message == WM_MOUSEMOVE)
|
||||||
{
|
{
|
||||||
Control* current = it->get();
|
// 顶层普通控件的 hover/tooltip 清理规则:
|
||||||
consume = current->handleEvent(msg);
|
// - 第一个命中的兄弟分支收到真实 WM_MOUSEMOVE;
|
||||||
if (consume)
|
// - 后续兄弟不再重新命中,只清理旧 hover / tooltip 等瞬时鼠标状态。
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
{
|
{
|
||||||
if (!SxIsNoisyMsg(msg.message))
|
Control* current = it->get();
|
||||||
SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
|
if (!consume)
|
||||||
<< SxMsgName(msg.message)
|
{
|
||||||
<< SX_T(" id=", " id=") << current->getId();
|
consume = current->handleEvent(msg);
|
||||||
break;
|
if (consume)
|
||||||
|
firstConsumer = current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current->clearTransientMouseState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||||
|
{
|
||||||
|
Control* current = it->get();
|
||||||
|
consume = current->handleEvent(msg);
|
||||||
|
if (consume)
|
||||||
|
{
|
||||||
|
firstConsumer = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstConsumer && !SxIsNoisyMsg(msg.message))
|
||||||
|
SX_LOGD("Event") << SX_T("事件被控件处理:", "Event consumed by control: ")
|
||||||
|
<< SxMsgName(msg.message)
|
||||||
|
<< SX_T(" id=", " id=") << firstConsumer->getId();
|
||||||
}
|
}
|
||||||
managedDispatchActive = false;
|
managedDispatchActive = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,37 +69,41 @@ public:
|
|||||||
mutable bool dialogOpen = false; // 项目内使用的状态位,对话框打开标志
|
mutable bool dialogOpen = false; // 项目内使用的状态位,对话框打开标志
|
||||||
|
|
||||||
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
|
// —— 构造/析构 ——(仅初始化成员;实际样式与子类化在 draw() 中完成)
|
||||||
|
// 创建纯色背景窗口
|
||||||
Window(int width, int height, int mode);
|
Window(int width, int height, int mode);
|
||||||
|
// 创建指定背景色窗口
|
||||||
Window(int width, int height, int mode, COLORREF bkcloc);
|
Window(int width, int height, int mode, COLORREF bkcloc);
|
||||||
|
// 创建带标题与背景色的窗口
|
||||||
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
Window(int width, int height, int mode, COLORREF bkcloc, std::string headline);
|
||||||
|
// 析构窗口并释放背景、控件与子类化资源
|
||||||
~Window();
|
~Window();
|
||||||
|
|
||||||
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
|
// —— 绘制与事件循环 ——(draw* 完成一次全量绘制;runEventLoop 驱动事件与统一收口)
|
||||||
void draw(); // 纯色背景版本
|
void draw(); // 以纯色背景执行一次全量绘制
|
||||||
void draw(std::string pImgFile); // 背景图版本
|
void draw(std::string pImgFile); // 以背景图执行一次全量绘制
|
||||||
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
|
int runEventLoop(); // 主事件循环(PeekMessage + 统一收口重绘)
|
||||||
|
|
||||||
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
|
// —— 背景/标题设置 ——(更换背景、背景色与标题;立即触发一次批量绘制)
|
||||||
void setBkImage(std::string pImgFile);
|
void setBkImage(std::string pImgFile); // 设置窗口背景图
|
||||||
void setBkcolor(COLORREF c);
|
void setBkcolor(COLORREF c); // 设置窗口纯色背景
|
||||||
void setHeadline(std::string headline);
|
void setHeadline(std::string headline); // 设置窗口标题
|
||||||
|
|
||||||
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
|
// —— 控件/对话框管理 ——(添加到容器,或做存在性判断)
|
||||||
void addControl(std::unique_ptr<Control> control);
|
void addControl(std::unique_ptr<Control> control); // 添加普通顶层控件
|
||||||
void addDialog(std::unique_ptr<Control> dialogs);
|
void addDialog(std::unique_ptr<Control> dialogs); // 添加非模态对话框
|
||||||
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const;
|
bool hasNonModalDialogWithCaption(const std::string& caption, const std::string& message) const; // 检查是否已有同 caption/message 的非模态对话框
|
||||||
|
|
||||||
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
|
// —— 访问器 ——(只读接口,供外部查询当前窗口/标题/背景等)
|
||||||
HWND getHwnd() const;
|
HWND getHwnd() const; // 获取窗口句柄
|
||||||
int getWidth() const;
|
int getWidth() const; // 获取当前有效客户区宽度
|
||||||
int getHeight() const;
|
int getHeight() const; // 获取当前有效客户区高度
|
||||||
int getPendingWidth() const;
|
int getPendingWidth() const; // 获取待应用宽度
|
||||||
int getPendingHeight() const;
|
int getPendingHeight() const; // 获取待应用高度
|
||||||
std::string getHeadline() const;
|
std::string getHeadline() const; // 获取窗口标题
|
||||||
COLORREF getBkcolor() const;
|
COLORREF getBkcolor() const; // 获取纯色背景
|
||||||
IMAGE* getBkImage() const;
|
IMAGE* getBkImage() const; // 获取背景图对象
|
||||||
std::string getBkImageFile() const;
|
std::string getBkImageFile() const; // 获取背景图路径
|
||||||
std::vector<std::unique_ptr<Control>>& getControls();
|
std::vector<std::unique_ptr<Control>>& getControls(); // 获取普通顶层控件列表
|
||||||
|
|
||||||
// —— 尺寸调整 / 托管重绘 ——(事件阶段登记,收口阶段提交)
|
// —— 尺寸调整 / 托管重绘 ——(事件阶段登记,收口阶段提交)
|
||||||
|
|
||||||
|
|||||||
+313
-111
@@ -1,13 +1,12 @@
|
|||||||
// StellarX 星垣GUI框架 - 测试用例
|
// StellarX 星垣GUI框架 - 测试用例
|
||||||
#include"StellarX.h"
|
#include"StellarX.h"
|
||||||
|
#include <vector>
|
||||||
#ifndef KEY
|
#ifndef KEY
|
||||||
#define KEY 5
|
#define KEY 4
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 5 == KEY
|
#if 5 == KEY
|
||||||
|
|
||||||
#include"StellarX.h"
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
StellarX::SxLogger::setGBK();
|
StellarX::SxLogger::setGBK();
|
||||||
@@ -16,199 +15,402 @@ int main()
|
|||||||
StellarX::SxLogger::Get().setLanguage(StellarX::SxLogLanguage::ZhCN);
|
StellarX::SxLogger::Get().setLanguage(StellarX::SxLogLanguage::ZhCN);
|
||||||
|
|
||||||
const COLORREF headerColor = RGB(232, 238, 245);
|
const COLORREF headerColor = RGB(232, 238, 245);
|
||||||
const COLORREF horizontalColor = RGB(217, 233, 252);
|
const COLORREF nestedZoneColor = RGB(217, 233, 252);
|
||||||
const COLORREF nestedColor = RGB(219, 244, 223);
|
const COLORREF level1Color = RGB(224, 244, 255);
|
||||||
const COLORREF verticalColor = RGB(255, 233, 205);
|
const COLORREF level2Color = RGB(222, 244, 228);
|
||||||
|
const COLORREF level3Color = RGB(255, 239, 215);
|
||||||
|
const COLORREF tabZoneColor = RGB(220, 241, 240);
|
||||||
|
const COLORREF behaviorZoneColor = RGB(243, 238, 214);
|
||||||
|
const COLORREF overlayZoneColor = RGB(255, 228, 206);
|
||||||
const COLORREF tableZoneColor = RGB(235, 226, 250);
|
const COLORREF tableZoneColor = RGB(235, 226, 250);
|
||||||
const COLORREF movingColor = RGB(249, 224, 232);
|
const COLORREF floatingColor = RGB(249, 224, 232);
|
||||||
|
const COLORREF buttonFalseColor = RGB(246, 247, 249);
|
||||||
|
const COLORREF buttonHoverColor = RGB(255, 225, 92);
|
||||||
|
const COLORREF buttonTrueColor = RGB(236, 138, 88);
|
||||||
|
|
||||||
Window win(1280, 780, 1, RGB(246, 248, 251), "StellarX 布局系统专项回归 KEY5");
|
Window win(1380, 880, 1, RGB(246, 248, 251), "StellarX KEY5 第二阶段专项回归");
|
||||||
|
|
||||||
auto header = std::make_unique<Canvas>(20, 20, 1240, 100);
|
auto configureTooltip = [](Button* button, const std::string& text)
|
||||||
|
{
|
||||||
|
button->enableTooltip(true);
|
||||||
|
button->setTooltipDelay(120);
|
||||||
|
button->setTooltipText(text);
|
||||||
|
};
|
||||||
|
auto makeTestButton = [&](int x, int y, int width, int height, const std::string& text)
|
||||||
|
{
|
||||||
|
auto button = std::make_unique<Button>(x, y, width, height, text, buttonTrueColor, buttonFalseColor, buttonHoverColor);
|
||||||
|
configureTooltip(button.get(), text);
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto header = std::make_unique<Canvas>(20, 20, 1340, 90);
|
||||||
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);
|
headerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
headerPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
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;
|
||||||
|
headerTitle->setDirty(true);
|
||||||
headerTitle->setTextdisap(true);
|
headerTitle->setTextdisap(true);
|
||||||
auto headerLine1 = std::make_unique<Label>(18, 46, "观察点 1:蓝色区域横向拉伸;绿色嵌套区域随父容器一起变化。");
|
auto headerLine1 = std::make_unique<Label>(18, 44, "A 三层嵌套 B TabControl C Label/跨容器 hover D 同父 overlay E Table/顶层浮层");
|
||||||
headerLine1->setTextdisap(true);
|
headerLine1->setTextdisap(true);
|
||||||
auto headerLine2 = std::make_unique<Label>(18, 70, "观察点 2:橙色区域纵向拉伸;紫色区域中 Table 只做横向拉伸;粉色区域整体随底边移动。");
|
auto headerLine2 = std::make_unique<Label>(18, 64, "重点看:stretch、父局部到世界坐标、tooltip 清理、overlay 补画、分页按钮与页码");
|
||||||
headerLine2->setTextdisap(true);
|
headerLine2->setTextdisap(true);
|
||||||
headerPtr->addControl(std::move(headerTitle));
|
headerPtr->addControl(std::move(headerTitle));
|
||||||
headerPtr->addControl(std::move(headerLine1));
|
headerPtr->addControl(std::move(headerLine1));
|
||||||
headerPtr->addControl(std::move(headerLine2));
|
headerPtr->addControl(std::move(headerLine2));
|
||||||
|
|
||||||
auto horizontalZone = std::make_unique<Canvas>(20, 140, 1240, 220);
|
auto nestedZone = std::make_unique<Canvas>(20, 130, 650, 280);
|
||||||
auto horizontalZonePtr = horizontalZone.get();
|
|
||||||
horizontalZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
|
||||||
horizontalZonePtr->setCanvasBkColor(horizontalColor);
|
|
||||||
horizontalZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
horizontalZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto horizontalTitle = std::make_unique<Label>(18, 10, "区域 A(蓝):顶层 Left+Right 拉伸,内部同时验证 Left only / Right only / NoAnchor / 嵌套 Canvas。");
|
|
||||||
horizontalTitle->setTextdisap(true);
|
|
||||||
auto fixedLabel = std::make_unique<Label>(18, 42, "固定参考标签:不设置锚点");
|
|
||||||
fixedLabel->setTextdisap(true);
|
|
||||||
|
|
||||||
auto leftFixedBtn = std::make_unique<Button>(18, 78, 130, 36, "左边固定");
|
|
||||||
leftFixedBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
leftFixedBtn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto rightFixedBtn = std::make_unique<Button>(1090, 78, 130, 36, "右边固定");
|
|
||||||
rightFixedBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
rightFixedBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto stretchBox = std::make_unique<TextBox>(178, 78, 892, 36, "左右拉伸输入框:窗口变宽时我会伸长");
|
|
||||||
stretchBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
stretchBox->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
|
||||||
|
|
||||||
auto nestedZone = std::make_unique<Canvas>(80, 126, 1080, 78);
|
|
||||||
auto nestedZonePtr = nestedZone.get();
|
auto nestedZonePtr = nestedZone.get();
|
||||||
nestedZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
nestedZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
nestedZonePtr->setCanvasBkColor(nestedColor);
|
nestedZonePtr->setCanvasBkColor(nestedZoneColor);
|
||||||
nestedZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
nestedZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
nestedZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
nestedZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
auto nestedTitle = std::make_unique<Label>(12, 10, "区域 A-1(绿):嵌套 Canvas。父容器拉伸后,这里的左右锚点和世界坐标都要同步更新。");
|
auto nestedTitle = std::make_unique<Label>(16, 16, "A 蓝:三层 Canvas 嵌套");
|
||||||
nestedTitle->setTextdisap(true);
|
nestedTitle->setTextdisap(true);
|
||||||
auto nestedLeftBtn = std::make_unique<Button>(18, 40, 120, 30, "内层左固定");
|
auto nestedHint = std::make_unique<Label>(16, 40, "看父局部到世界坐标链。");
|
||||||
|
nestedHint->setTextdisap(true);
|
||||||
|
|
||||||
|
auto nestedLeftBtn = makeTestButton(20, 74, 110, 34, "左固定");
|
||||||
nestedLeftBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
nestedLeftBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
nestedLeftBtn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
nestedLeftBtn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
||||||
auto nestedRightBtn = std::make_unique<Button>(942, 40, 120, 30, "内层右固定");
|
|
||||||
|
auto nestedStretchBox = std::make_unique<TextBox>(150, 74, 350, 34, "A 区横向拉伸输入框");
|
||||||
|
nestedStretchBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
nestedStretchBox->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto nestedRightBtn = makeTestButton(520, 74, 110, 34, "右固定");
|
||||||
nestedRightBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
nestedRightBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
nestedRightBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
nestedRightBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
|
auto level1 = std::make_unique<Canvas>(18, 126, 614, 134);
|
||||||
|
auto level1Ptr = level1.get();
|
||||||
|
level1Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
level1Ptr->setCanvasBkColor(level1Color);
|
||||||
|
level1Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level1Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto level1Title = std::make_unique<Label>(14, 10, "A-1 青:第一层");
|
||||||
|
level1Title->setTextdisap(true);
|
||||||
|
auto level1Btn = makeTestButton(18, 42, 110, 30, "内层左");
|
||||||
|
level1Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level1Btn->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
|
auto level2 = std::make_unique<Canvas>(142, 36, 454, 84);
|
||||||
|
auto level2Ptr = level2.get();
|
||||||
|
level2Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
level2Ptr->setCanvasBkColor(level2Color);
|
||||||
|
level2Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level2Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto level2Title = std::make_unique<Label>(12, 8, "A-2 绿:第二层");
|
||||||
|
level2Title->setTextdisap(true);
|
||||||
|
auto level2Btn = makeTestButton(332, 8, 108, 28, "右固定");
|
||||||
|
level2Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level2Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
|
auto level3 = std::make_unique<Canvas>(16, 42, 422, 28);
|
||||||
|
auto level3Ptr = level3.get();
|
||||||
|
level3Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
level3Ptr->setCanvasBkColor(level3Color);
|
||||||
|
level3Ptr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level3Ptr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto level3Label = std::make_unique<Label>(10, 6, "A-3 橙:第三层");
|
||||||
|
level3Label->setTextdisap(true);
|
||||||
|
auto level3Btn = makeTestButton(312, 2, 98, 24, "右按钮");
|
||||||
|
level3Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
level3Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(level3Btn.get(), "第三层 Canvas 里的右固定按钮");
|
||||||
|
|
||||||
|
level3Ptr->addControl(std::move(level3Label));
|
||||||
|
level3Ptr->addControl(std::move(level3Btn));
|
||||||
|
level2Ptr->addControl(std::move(level2Title));
|
||||||
|
level2Ptr->addControl(std::move(level2Btn));
|
||||||
|
level2Ptr->addControl(std::move(level3));
|
||||||
|
level1Ptr->addControl(std::move(level1Title));
|
||||||
|
level1Ptr->addControl(std::move(level1Btn));
|
||||||
|
level1Ptr->addControl(std::move(level2));
|
||||||
|
|
||||||
nestedZonePtr->addControl(std::move(nestedTitle));
|
nestedZonePtr->addControl(std::move(nestedTitle));
|
||||||
|
nestedZonePtr->addControl(std::move(nestedHint));
|
||||||
nestedZonePtr->addControl(std::move(nestedLeftBtn));
|
nestedZonePtr->addControl(std::move(nestedLeftBtn));
|
||||||
|
nestedZonePtr->addControl(std::move(nestedStretchBox));
|
||||||
nestedZonePtr->addControl(std::move(nestedRightBtn));
|
nestedZonePtr->addControl(std::move(nestedRightBtn));
|
||||||
|
nestedZonePtr->addControl(std::move(level1));
|
||||||
|
|
||||||
horizontalZonePtr->addControl(std::move(horizontalTitle));
|
auto tabZone = std::make_unique<Canvas>(690, 130, 670, 280);
|
||||||
horizontalZonePtr->addControl(std::move(fixedLabel));
|
auto tabZonePtr = tabZone.get();
|
||||||
horizontalZonePtr->addControl(std::move(leftFixedBtn));
|
tabZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
horizontalZonePtr->addControl(std::move(rightFixedBtn));
|
tabZonePtr->setCanvasBkColor(tabZoneColor);
|
||||||
horizontalZonePtr->addControl(std::move(stretchBox));
|
tabZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
horizontalZonePtr->addControl(std::move(nestedZone));
|
tabZonePtr->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
auto verticalZone = std::make_unique<Canvas>(20, 380, 420, 278);
|
auto tabTitle = std::make_unique<Label>(16, 16, "B 青:TabControl 外层 resize");
|
||||||
auto verticalZonePtr = verticalZone.get();
|
tabTitle->setTextdisap(true);
|
||||||
verticalZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
auto tabHint = std::make_unique<Label>(16, 40, "看页签、页面和页内控件。");
|
||||||
verticalZonePtr->setCanvasBkColor(verticalColor);
|
tabHint->setTextdisap(true);
|
||||||
verticalZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
verticalZonePtr->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::Bottom);
|
|
||||||
|
|
||||||
auto verticalTitle = std::make_unique<Label>(18, 14, "区域 B(橙):Top+Bottom 拉伸,Bottom only 控件要随底边移动。");
|
auto tabControl = std::make_unique<TabControl>(18, 60, 634, 200);
|
||||||
verticalTitle->setTextdisap(true);
|
|
||||||
verticalTitle->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
verticalTitle->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto verticalHint = std::make_unique<Label>(18, 88, "中部参考标签:不设置锚点,用来观察固定位置。");
|
|
||||||
verticalHint->setTextdisap(true);
|
|
||||||
|
|
||||||
auto bottomBox = std::make_unique<TextBox>(22, 204, 220, 34, "底边固定输入框");
|
|
||||||
bottomBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
bottomBox->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto bottomBtn = std::make_unique<Button>(270, 204, 120, 34, "底边固定");
|
|
||||||
bottomBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
bottomBtn->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
verticalZonePtr->addControl(std::move(verticalTitle));
|
|
||||||
verticalZonePtr->addControl(std::move(verticalHint));
|
|
||||||
verticalZonePtr->addControl(std::move(bottomBox));
|
|
||||||
verticalZonePtr->addControl(std::move(bottomBtn));
|
|
||||||
|
|
||||||
auto tabControl = std::make_unique<TabControl>(460, 380, 800, 168);
|
|
||||||
auto tabControlPtr = tabControl.get();
|
auto tabControlPtr = tabControl.get();
|
||||||
tabControlPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
tabControlPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
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(30);
|
||||||
tabControlPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
tabControlPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
tabControlPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
tabControlPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
auto tabPage1 = std::make_unique<Canvas>(0, 0, 800, 132);
|
auto tabPage1 = std::make_unique<Canvas>(0, 0, 634, 170);
|
||||||
auto tabPage1Ptr = tabPage1.get();
|
auto tabPage1Ptr = tabPage1.get();
|
||||||
tabPage1Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
tabPage1Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
tabPage1Ptr->setCanvasBkColor(RGB(241, 247, 255));
|
tabPage1Ptr->setCanvasBkColor(RGB(241, 247, 255));
|
||||||
auto tabPage2 = std::make_unique<Canvas>(0, 0, 800, 132);
|
|
||||||
|
auto tabPage2 = std::make_unique<Canvas>(0, 0, 634, 170);
|
||||||
auto tabPage2Ptr = tabPage2.get();
|
auto tabPage2Ptr = tabPage2.get();
|
||||||
tabPage2Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
tabPage2Ptr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
tabPage2Ptr->setCanvasBkColor(RGB(248, 244, 236));
|
tabPage2Ptr->setCanvasBkColor(RGB(248, 244, 236));
|
||||||
|
|
||||||
auto page1Label = std::make_unique<Label>(16, 12, "区域 C(页 1):TabControl 外层参与统一解算,页内 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>(20, 52, 650, 34, "页内左右拉伸输入框");
|
auto page1Box = std::make_unique<TextBox>(18, 44, 468, 34, "页 1 左右拉伸输入框");
|
||||||
page1Box->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
page1Box->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
page1Box->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
page1Box->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
auto page1Btn = std::make_unique<Button>(692, 52, 90, 34, "右固定");
|
auto page1Btn = makeTestButton(504, 44, 96, 34, "右固定");
|
||||||
page1Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
page1Btn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
page1Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
page1Btn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(page1Btn.get(), "页 1 右固定按钮");
|
||||||
|
|
||||||
|
auto page1InnerCanvas = std::make_unique<Canvas>(18, 92, 582, 56);
|
||||||
|
auto page1InnerCanvasPtr = page1InnerCanvas.get();
|
||||||
|
page1InnerCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
page1InnerCanvasPtr->setCanvasBkColor(RGB(223, 236, 248));
|
||||||
|
page1InnerCanvasPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
page1InnerCanvasPtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto page1InnerLabel = std::make_unique<Label>(10, 8, "页 1 内层 Canvas");
|
||||||
|
page1InnerLabel->setTextdisap(true);
|
||||||
|
auto page1InnerBtn = makeTestButton(462, 14, 108, 28, "右按钮");
|
||||||
|
page1InnerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
page1InnerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(page1InnerBtn.get(), "页 1 内层 Canvas 按钮");
|
||||||
|
|
||||||
|
page1InnerCanvasPtr->addControl(std::move(page1InnerLabel));
|
||||||
|
page1InnerCanvasPtr->addControl(std::move(page1InnerBtn));
|
||||||
tabPage1Ptr->addControl(std::move(page1Label));
|
tabPage1Ptr->addControl(std::move(page1Label));
|
||||||
tabPage1Ptr->addControl(std::move(page1Box));
|
tabPage1Ptr->addControl(std::move(page1Box));
|
||||||
tabPage1Ptr->addControl(std::move(page1Btn));
|
tabPage1Ptr->addControl(std::move(page1Btn));
|
||||||
|
tabPage1Ptr->addControl(std::move(page1InnerCanvas));
|
||||||
|
|
||||||
auto page2Label = std::make_unique<Label>(16, 18, "区域 C(页 2):切页后再次拖动窗口,页签栏和页区域都应保持稳定。");
|
auto page2Label = std::make_unique<Label>(14, 12, "页 2:切页后继续测 hover 和 resize。");
|
||||||
page2Label->setTextdisap(true);
|
page2Label->setTextdisap(true);
|
||||||
auto page2Btn = std::make_unique<Button>(20, 60, 120, 34, "普通按钮");
|
auto page2Btn = makeTestButton(18, 44, 126, 34, "Tooltip 按钮");
|
||||||
|
configureTooltip(page2Btn.get(), "页 2 普通按钮 Tooltip");
|
||||||
|
|
||||||
|
auto page2InnerCanvas = std::make_unique<Canvas>(166, 36, 434, 84);
|
||||||
|
auto page2InnerCanvasPtr = page2InnerCanvas.get();
|
||||||
|
page2InnerCanvasPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
page2InnerCanvasPtr->setCanvasBkColor(RGB(235, 229, 214));
|
||||||
|
|
||||||
|
auto page2InnerLabel = std::make_unique<Label>(10, 8, "页 2 内层 Canvas");
|
||||||
|
page2InnerLabel->setTextdisap(true);
|
||||||
|
auto page2InnerBtn = makeTestButton(286, 42, 126, 28, "右侧 Tooltip");
|
||||||
|
page2InnerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
page2InnerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(page2InnerBtn.get(), "页 2 内层按钮 Tooltip");
|
||||||
|
|
||||||
|
page2InnerCanvasPtr->addControl(std::move(page2InnerLabel));
|
||||||
|
page2InnerCanvasPtr->addControl(std::move(page2InnerBtn));
|
||||||
tabPage2Ptr->addControl(std::move(page2Label));
|
tabPage2Ptr->addControl(std::move(page2Label));
|
||||||
tabPage2Ptr->addControl(std::move(page2Btn));
|
tabPage2Ptr->addControl(std::move(page2Btn));
|
||||||
|
tabPage2Ptr->addControl(std::move(page2InnerCanvas));
|
||||||
|
|
||||||
tabControlPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 120, 28, "布局页"), std::move(tabPage1)));
|
auto tabBtn1 = std::make_unique<Button>(0, 0, 108, 30, "布局页");
|
||||||
tabControlPtr->add(std::make_pair(std::make_unique<Button>(0, 0, 120, 28, "切换页"), std::move(tabPage2)));
|
auto tabBtn2 = std::make_unique<Button>(0, 0, 108, 30, "交互页");
|
||||||
|
configureTooltip(tabBtn1.get(), "布局页页签按钮");
|
||||||
|
configureTooltip(tabBtn2.get(), "交互页页签按钮");
|
||||||
|
tabControlPtr->add(std::make_pair(std::move(tabBtn1), std::move(tabPage1)));
|
||||||
|
tabControlPtr->add(std::make_pair(std::move(tabBtn2), std::move(tabPage2)));
|
||||||
tabControlPtr->setActiveIndex(0);
|
tabControlPtr->setActiveIndex(0);
|
||||||
|
|
||||||
auto tableZone = std::make_unique<Canvas>(460, 566, 800, 132);
|
tabZonePtr->addControl(std::move(tabTitle));
|
||||||
|
tabZonePtr->addControl(std::move(tabHint));
|
||||||
|
tabZonePtr->addControl(std::move(tabControl));
|
||||||
|
|
||||||
|
auto behaviorZone = std::make_unique<Canvas>(20, 430, 650, 190);
|
||||||
|
auto behaviorZonePtr = behaviorZone.get();
|
||||||
|
behaviorZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
behaviorZonePtr->setCanvasBkColor(behaviorZoneColor);
|
||||||
|
|
||||||
|
auto behaviorTitle = std::make_unique<Label>(16, 16, "C 米:Label 内容驱动 + 跨容器 hover");
|
||||||
|
behaviorTitle->setTextdisap(true);
|
||||||
|
|
||||||
|
auto dynamicLabel = std::make_unique<Label>(20, 52, "动态标签:默认短文本");
|
||||||
|
auto dynamicLabelPtr = dynamicLabel.get();
|
||||||
|
dynamicLabelPtr->textStyle.nHeight = 22;
|
||||||
|
dynamicLabelPtr->setDirty(true);
|
||||||
|
dynamicLabelPtr->setTextdisap(true);
|
||||||
|
|
||||||
|
auto behaviorHint = std::make_unique<Label>(20, 82, "改样式后会手动 setDirty(true)。");
|
||||||
|
behaviorHint->setTextdisap(true);
|
||||||
|
|
||||||
|
std::vector<std::string> labelTexts =
|
||||||
|
{
|
||||||
|
"动态标签:默认短文本",
|
||||||
|
"动态标签:切到更长一点的文本",
|
||||||
|
"动态标签:短"
|
||||||
|
};
|
||||||
|
int labelIndex = 0;
|
||||||
|
bool styleToggle = false;
|
||||||
|
|
||||||
|
auto changeTextBtn = makeTestButton(20, 120, 120, 34, "改文本");
|
||||||
|
auto changeStyleBtn = makeTestButton(156, 120, 150, 34, "改样式");
|
||||||
|
changeTextBtn->setOnClickListener([&]()
|
||||||
|
{
|
||||||
|
labelIndex = (labelIndex + 1) % static_cast<int>(labelTexts.size());
|
||||||
|
dynamicLabelPtr->setText(labelTexts[labelIndex]);
|
||||||
|
});
|
||||||
|
changeStyleBtn->setOnClickListener([&]()
|
||||||
|
{
|
||||||
|
styleToggle = !styleToggle;
|
||||||
|
dynamicLabelPtr->textStyle.nHeight = styleToggle ? 30 : 22;
|
||||||
|
dynamicLabelPtr->textStyle.color = styleToggle ? RGB(184, 87, 44) : RGB(0, 0, 0);
|
||||||
|
dynamicLabelPtr->setDirty(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto innerHoverBtn = makeTestButton(506, 120, 118, 34, "容器内按钮");
|
||||||
|
innerHoverBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
innerHoverBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(innerHoverBtn.get(), "Canvas 内按钮 Tooltip");
|
||||||
|
|
||||||
|
behaviorZonePtr->addControl(std::move(behaviorTitle));
|
||||||
|
behaviorZonePtr->addControl(std::move(dynamicLabel));
|
||||||
|
behaviorZonePtr->addControl(std::move(behaviorHint));
|
||||||
|
behaviorZonePtr->addControl(std::move(changeTextBtn));
|
||||||
|
behaviorZonePtr->addControl(std::move(changeStyleBtn));
|
||||||
|
behaviorZonePtr->addControl(std::move(innerHoverBtn));
|
||||||
|
|
||||||
|
auto overlayZone = std::make_unique<Canvas>(690, 430, 670, 190);
|
||||||
|
auto overlayZonePtr = overlayZone.get();
|
||||||
|
overlayZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
overlayZonePtr->setCanvasBkColor(overlayZoneColor);
|
||||||
|
overlayZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
overlayZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
|
auto overlayTitle = std::make_unique<Label>(16, 16, "D 橙:Top+Bottom + 同父 overlay");
|
||||||
|
overlayTitle->setTextdisap(true);
|
||||||
|
|
||||||
|
auto lowerLayer = std::make_unique<Canvas>(20, 56, 356, 104);
|
||||||
|
auto lowerLayerPtr = lowerLayer.get();
|
||||||
|
lowerLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
lowerLayerPtr->setCanvasBkColor(RGB(255, 245, 231));
|
||||||
|
lowerLayerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
lowerLayerPtr->setAnchor(StellarX::Anchor::Top, StellarX::Anchor::Bottom);
|
||||||
|
|
||||||
|
auto lowerTitle = std::make_unique<Label>(12, 8, "D-1 下层区");
|
||||||
|
lowerTitle->setTextdisap(true);
|
||||||
|
auto lowerBox = std::make_unique<TextBox>(12, 34, 222, 30, "下层区输入框");
|
||||||
|
lowerBox->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
lowerBox->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
auto lowerBtn = makeTestButton(248, 34, 92, 30, "下层按钮");
|
||||||
|
lowerBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
lowerBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(lowerBtn.get(), "下层 Canvas 按钮");
|
||||||
|
|
||||||
|
lowerLayerPtr->addControl(std::move(lowerTitle));
|
||||||
|
lowerLayerPtr->addControl(std::move(lowerBox));
|
||||||
|
lowerLayerPtr->addControl(std::move(lowerBtn));
|
||||||
|
|
||||||
|
auto upperLayer = std::make_unique<Canvas>(262, 94, 382, 78);
|
||||||
|
auto upperLayerPtr = upperLayer.get();
|
||||||
|
upperLayerPtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
|
upperLayerPtr->setCanvasBkColor(RGB(255, 224, 206));
|
||||||
|
upperLayerPtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
upperLayerPtr->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
|
auto upperTitle = std::make_unique<Label>(12, 8, "D-2 上层区");
|
||||||
|
upperTitle->setTextdisap(true);
|
||||||
|
auto upperBtn = makeTestButton(244, 38, 122, 28, "上层 Tooltip");
|
||||||
|
upperBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
upperBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(upperBtn.get(), "上层 Canvas 按钮");
|
||||||
|
|
||||||
|
upperLayerPtr->addControl(std::move(upperTitle));
|
||||||
|
upperLayerPtr->addControl(std::move(upperBtn));
|
||||||
|
|
||||||
|
auto overlayBottomBtn = makeTestButton(526, 148, 118, 30, "底边固定");
|
||||||
|
overlayBottomBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
overlayBottomBtn->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
||||||
|
|
||||||
|
overlayZonePtr->addControl(std::move(overlayTitle));
|
||||||
|
overlayZonePtr->addControl(std::move(lowerLayer));
|
||||||
|
overlayZonePtr->addControl(std::move(upperLayer));
|
||||||
|
overlayZonePtr->addControl(std::move(overlayBottomBtn));
|
||||||
|
|
||||||
|
auto tableZone = std::make_unique<Canvas>(20, 640, 980, 200);
|
||||||
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);
|
tableZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
tableZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
tableZonePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
||||||
|
|
||||||
auto tableTitle = std::make_unique<Label>(16, 10, "区域 D(紫):Table 当前只回归 X 轴拉伸,不做 Y 轴拉伸。");
|
auto tableTitle = std::make_unique<Label>(16, 12, "E 紫:Table 仅 X Stretch");
|
||||||
tableTitle->setTextdisap(true);
|
tableTitle->setTextdisap(true);
|
||||||
auto table = std::make_unique<Table>(20, 46);
|
auto tableHint = std::make_unique<Label>(16, 34, "看翻页按钮、页码和顶层浮层补画。");
|
||||||
|
tableHint->setTextdisap(true);
|
||||||
|
|
||||||
|
auto table = std::make_unique<Table>(20, 62);
|
||||||
auto tablePtr = table.get();
|
auto tablePtr = table.get();
|
||||||
tablePtr->setHeaders({ "编号", "回归点", "预期" });
|
tablePtr->setHeaders({ "编号", "回归点", "预期" });
|
||||||
tablePtr->setData({
|
tablePtr->setData({
|
||||||
{"01", "左右拉伸", "窗口变宽后列宽一起增大"},
|
{"01", "左右拉伸", "窗口变宽后列宽一起增大"},
|
||||||
{"02", "纵向固定", "表格高度保持当前设计值"},
|
{"02", "分页按钮", "hover / click / leave 都能及时刷新"},
|
||||||
{"03", "页脚重排", "分页按钮和页码随宽度重排"}
|
{"03", "页码标签", "整表重绘后不会消失"},
|
||||||
|
{"04", "顶层浮层", "下层重绘后上层还能补回来"}
|
||||||
});
|
});
|
||||||
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);
|
tablePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
tablePtr->setAnchor(StellarX::Anchor::Left, StellarX::Anchor::Right);
|
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(table));
|
tableZonePtr->addControl(std::move(table));
|
||||||
|
|
||||||
auto movingZone = std::make_unique<Canvas>(934, 706, 326, 70);
|
auto topHoverBtn = makeTestButton(646, 550, 118, 34, "顶层按钮");
|
||||||
auto movingZonePtr = movingZone.get();
|
configureTooltip(topHoverBtn.get(), "顶层按钮 Tooltip");
|
||||||
movingZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
|
||||||
movingZonePtr->setCanvasBkColor(movingColor);
|
|
||||||
movingZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
|
||||||
movingZonePtr->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
|
||||||
|
|
||||||
auto movingLabel = std::make_unique<Label>(14, 10, "区域 E(粉):Bottom only 容器。");
|
auto floatingZone = std::make_unique<Canvas>(900, 702, 460, 138);
|
||||||
movingLabel->setTextdisap(true);
|
auto floatingZonePtr = floatingZone.get();
|
||||||
auto movingHint = std::make_unique<Label>(14, 30, "窗口变高时整体向下移动。");
|
floatingZonePtr->setShape(StellarX::ControlShape::ROUND_RECTANGLE);
|
||||||
movingHint->setTextdisap(true);
|
floatingZonePtr->setCanvasBkColor(floatingColor);
|
||||||
auto movingBtn = std::make_unique<Button>(208, 38, 100, 24, "右固定");
|
floatingZonePtr->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
movingBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
floatingZonePtr->setAnchor(StellarX::Anchor::Bottom, StellarX::Anchor::NoAnchor);
|
||||||
movingBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
|
||||||
movingZonePtr->addControl(std::move(movingLabel));
|
auto floatingTitle = std::make_unique<Label>(16, 12, "F 粉:Bottom only 浮层");
|
||||||
movingZonePtr->addControl(std::move(movingHint));
|
floatingTitle->setTextdisap(true);
|
||||||
movingZonePtr->addControl(std::move(movingBtn));
|
auto floatingHint = std::make_unique<Label>(16, 36, "看 Table 下层重绘后的补画。");
|
||||||
|
floatingHint->setTextdisap(true);
|
||||||
|
auto floatingBtn = makeTestButton(322, 88, 118, 30, "右固定");
|
||||||
|
floatingBtn->setLayoutMode(StellarX::LayoutMode::AnchorToEdges);
|
||||||
|
floatingBtn->setAnchor(StellarX::Anchor::Right, StellarX::Anchor::NoAnchor);
|
||||||
|
configureTooltip(floatingBtn.get(), "粉色浮层里的按钮");
|
||||||
|
|
||||||
|
floatingZonePtr->addControl(std::move(floatingTitle));
|
||||||
|
floatingZonePtr->addControl(std::move(floatingHint));
|
||||||
|
floatingZonePtr->addControl(std::move(floatingBtn));
|
||||||
|
|
||||||
win.addControl(std::move(header));
|
win.addControl(std::move(header));
|
||||||
win.addControl(std::move(horizontalZone));
|
win.addControl(std::move(nestedZone));
|
||||||
win.addControl(std::move(verticalZone));
|
win.addControl(std::move(tabZone));
|
||||||
win.addControl(std::move(tabControl));
|
win.addControl(std::move(behaviorZone));
|
||||||
|
win.addControl(std::move(topHoverBtn));
|
||||||
|
win.addControl(std::move(overlayZone));
|
||||||
win.addControl(std::move(tableZone));
|
win.addControl(std::move(tableZone));
|
||||||
win.addControl(std::move(movingZone));
|
win.addControl(std::move(floatingZone));
|
||||||
|
|
||||||
win.draw();
|
win.draw();
|
||||||
return win.runEventLoop();
|
return win.runEventLoop();
|
||||||
@@ -231,7 +433,7 @@ int main()
|
|||||||
table->setHeaders({ "name","age","seorc","home" });
|
table->setHeaders({ "name","age","seorc","home" });
|
||||||
table->setData({
|
table->setData({
|
||||||
{"zhangsan","20","99.99","wadsacafadsawd"},
|
{"zhangsan","20","99.99","wadsacafadsawd"},
|
||||||
{"lisi","20","99.99","wadsacafadsawd"},
|
{"lisi","20","99.99","wadsacafadssssssssssssssssssssssssssssssssssssssssssssssssssawd"},
|
||||||
{"wangwu","20","99.99","wadsacafadsawd"},
|
{"wangwu","20","99.99","wadsacafadsawd"},
|
||||||
{"zhaoliu","20","99.99","wadsacafadsawd"},
|
{"zhaoliu","20","99.99","wadsacafadsawd"},
|
||||||
{"1","20","99.99","wadsacafadsawd" },
|
{"1","20","99.99","wadsacafadsawd" },
|
||||||
|
|||||||
Reference in New Issue
Block a user