289 lines
8.0 KiB
C++
289 lines
8.0 KiB
C++
#include "Control.h"
|
||
#include "SxLog.h"
|
||
#include<assert.h>
|
||
#include "Window.h"
|
||
|
||
StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text)
|
||
{
|
||
{
|
||
nHeight = text.nHeight;
|
||
nWidth = text.nWidth;
|
||
lpszFace = text.lpszFace;
|
||
color = text.color;
|
||
nEscapement = text.nEscapement;
|
||
nOrientation = text.nOrientation;
|
||
nWeight = text.nWeight;
|
||
bItalic = text.bItalic;
|
||
bUnderline = text.bUnderline;
|
||
bStrikeOut = text.bStrikeOut;
|
||
return *this;
|
||
}
|
||
}
|
||
|
||
bool StellarX::ControlText::operator!=(const ControlText& text)
|
||
{
|
||
if(nHeight != text.nHeight)
|
||
return true;
|
||
else if (nWidth != text.nWidth)
|
||
return true;
|
||
else if (lpszFace != text.lpszFace)
|
||
return true;
|
||
else if (color != text.color)
|
||
return true;
|
||
else if (nEscapement != text.nEscapement)
|
||
return true;
|
||
else if (nOrientation != text.nOrientation)
|
||
return true;
|
||
else if (nWeight != text.nWeight)
|
||
return true;
|
||
else if (bItalic != text.bItalic)
|
||
return true;
|
||
else if (bUnderline != text.bUnderline)
|
||
return true;
|
||
else if (bStrikeOut != text.bStrikeOut)
|
||
return true;
|
||
return false;
|
||
}
|
||
void Control::setIsVisible(bool show)
|
||
{
|
||
SX_LOGD("Control") << SX_T("重置可见状态: id=", "setIsVisible: id=")
|
||
<< id
|
||
<< " show=" << (show ? 1 : 0);
|
||
|
||
if (this->show == show)
|
||
return;
|
||
|
||
this->show = show;
|
||
this->dirty = true;
|
||
|
||
if (!show)
|
||
{
|
||
// 隐藏:擦除自己在屏幕上的内容,并释放快照
|
||
discardBackground();
|
||
return;
|
||
}
|
||
|
||
// 显示:不在这里 requestRepaint(避免父容器快照未就绪时子控件抢跑 draw,污染快照)
|
||
// 仅向上标脏,让事件收口阶段由容器统一重绘。
|
||
if (parent)
|
||
parent->setDirty(true);
|
||
}
|
||
|
||
void Control::onWindowResize()
|
||
{
|
||
SX_LOGD("Layout") << SX_T("尺寸变化:id=", "onWindowResize: id=") << id
|
||
<< SX_T(" -> 丢背景快照 + 标脏", " -> discardSnap + dirty");
|
||
|
||
// 自己:丢快照 + 标脏
|
||
invalidateBackgroundSnapshot();
|
||
setDirty(true);
|
||
}
|
||
void Control::setLayoutMode(StellarX::LayoutMode layoutMode_)
|
||
{
|
||
this->layoutMode = layoutMode_;
|
||
}
|
||
void Control::setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2)
|
||
{
|
||
this->anchor_1 = anchor_1;
|
||
this->anchor_2 = anchor_2;
|
||
}
|
||
StellarX::Anchor Control::getAnchor_1() const
|
||
{
|
||
return this->anchor_1;
|
||
}
|
||
StellarX::Anchor Control::getAnchor_2() const
|
||
{
|
||
return this->anchor_2;
|
||
}
|
||
StellarX::LayoutMode Control::getLayoutMode() const
|
||
{
|
||
return this->layoutMode;
|
||
}
|
||
// 保存当前的绘图状态(字体、颜色、线型等)
|
||
// 在控件绘制前调用,确保不会影响全局绘图状态
|
||
void Control::saveStyle()
|
||
{
|
||
|
||
gettextstyle(¤tFont); // 获取当前字体样式
|
||
currentColor = gettextcolor(); // 获取当前字体颜色
|
||
currentBorderColor = getlinecolor(); //保存当前边框颜色
|
||
getlinestyle(¤tLineStyle); //保存当前线型
|
||
currentBkColor = getfillcolor(); //保存当前填充色
|
||
}
|
||
// 恢复之前保存的绘图状态
|
||
// 在控件绘制完成后调用,恢复全局绘图状态
|
||
void Control::restoreStyle()
|
||
{
|
||
settextstyle(¤tFont); // 恢复默认字体样式
|
||
settextcolor(currentColor); // 恢复默认字体颜色
|
||
setfillcolor(currentBkColor);
|
||
setlinestyle(¤tLineStyle);
|
||
setlinecolor(currentBorderColor);
|
||
setfillstyle(BS_SOLID);//恢复填充
|
||
}
|
||
|
||
void Control::requestRepaint(Control* parent)
|
||
{
|
||
if (shouldDeferManagedRepaint())
|
||
{
|
||
// 托管路径:当前正在 Window 的事件分发阶段,不能立即绘制;
|
||
// 这里只登记 source,真正的 root 选择由 Window 在 requestManagedRepaint 中完成。
|
||
if (auto* host = getHostWindow())
|
||
host->requestManagedRepaint(this);
|
||
return;
|
||
}
|
||
|
||
// 说明:
|
||
// - 常规路径:子控件调用 requestRepaint(this->parent),然后 parent 负责局部重绘(Canvas/TabControl override)
|
||
// - 兜底路径:如果某个“容器控件”没 override requestRepaint,就会出现 parent==this 的递归风险
|
||
// 此时我们改为向更上层冒泡,直到根重绘。
|
||
if (parent == this)
|
||
{
|
||
SX_LOGW("Dirty")
|
||
<< SX_T("requestRepaint(默认容器兜底):id=", "requestRepaint(default-container-fallback): id=")
|
||
<< id
|
||
<< SX_T(",parent==this,向上层 parent 继续冒泡", " parent==this, bubble to upper parent");
|
||
|
||
if (this->parent) this->parent->requestRepaint(this->parent);
|
||
else onRequestRepaintAsRoot();
|
||
return;
|
||
}
|
||
|
||
SX_LOG_TRACE("Dirty") << SX_T("请求重绘:id=","requestRepaint: id=") << id << " parent=" << (parent ? parent->getId() : "null");
|
||
|
||
if (parent) parent->requestRepaint(parent); // 交给容器处理(容器可局部重绘)
|
||
else onRequestRepaintAsRoot(); // 根兜底
|
||
}
|
||
|
||
void Control::onRequestRepaintAsRoot()
|
||
{
|
||
if (shouldDeferManagedRepaint())
|
||
{
|
||
// 即使已经冒泡到 root,只要还在托管分发期,也不能直接绘制;
|
||
// 仍然回到 Window 做统一提交。
|
||
if (auto* host = getHostWindow())
|
||
host->requestManagedRepaint(this);
|
||
return;
|
||
}
|
||
|
||
SX_LOG_TRACE("Dirty")
|
||
<< SX_T("触发根重绘:id=", "onRequestRepaintAsRoot: id=") << id
|
||
<< SX_T("(从根节点开始重画)", " (root repaint)");
|
||
|
||
|
||
discardBackground();
|
||
setDirty(true);
|
||
draw(); // 只有“无父”时才允许立即画,不会被谁覆盖
|
||
}
|
||
|
||
bool Control::shouldDeferManagedRepaint() const
|
||
{
|
||
Window* host = getHostWindow();
|
||
return host && host->isManagedDispatchActive();
|
||
}
|
||
|
||
// 获取宿主 Window:
|
||
// - 顶层控件由 Window/addDialog 直接注入;
|
||
// - 子控件没有直接注入时,沿 parent 链向上回溯即可。
|
||
Window* Control::getHostWindow() const
|
||
{
|
||
if (hostWindow)
|
||
return hostWindow;
|
||
return parent ? parent->getHostWindow() : nullptr;
|
||
}
|
||
|
||
// 托管重绘 root 选择规则:
|
||
// - 对于直接挂在 Window 下的控件,root 就是它自己;
|
||
// - 对于嵌套在 Canvas/TabControl/Dialog 内的子控件,沿 parent 向上找到与宿主 Window 同属一棵树的最上层控件。
|
||
Control* Control::getManagedRepaintRoot()
|
||
{
|
||
Control* root = this;
|
||
Window* host = getHostWindow();
|
||
while (root->parent && root->parent->getHostWindow() == host)
|
||
root = root->parent;
|
||
return root;
|
||
}
|
||
|
||
RECT Control::getBoundsRect() const
|
||
{
|
||
RECT rc{};
|
||
rc.left = x;
|
||
rc.top = y;
|
||
rc.right = x + width;
|
||
rc.bottom = y + height;
|
||
return rc;
|
||
}
|
||
|
||
bool Control::canCommitManagedPartialRepaint() const
|
||
{
|
||
// 基类默认不承诺自己能安全做局部提交;
|
||
// 只有 Canvas / TabControl / Dialog 这类“拥有完整背景语义”的 root 才会 override 为 true。
|
||
return false;
|
||
}
|
||
|
||
void Control::commitManagedRepaint()
|
||
{
|
||
if (!show)
|
||
return;
|
||
// 基类兜底:如果没有更具体的容器实现,就按根级重绘处理。
|
||
if (dirty)
|
||
onRequestRepaintAsRoot();
|
||
}
|
||
|
||
void Control::saveBackground(int x, int y, int w, int h)
|
||
{
|
||
|
||
if (w <= 0 || h <= 0) return;
|
||
saveBkX = x; saveBkY = y; saveWidth = w; saveHeight = h;
|
||
if (saveBkImage)
|
||
{
|
||
//尺寸变了才重建,避免反复 new/delete
|
||
if (saveBkImage->getwidth() != w || saveBkImage->getheight() != h)
|
||
{
|
||
SX_LOGD("Snap") <<SX_T("重新保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||
|
||
saveBkImage.reset();
|
||
}
|
||
}
|
||
else
|
||
SX_LOGD("Snap") << SX_T("保存背景快照:id=", "saveBackground rebuild: id=") << id << " size=(" << w << "x" << h << ")";
|
||
if (!saveBkImage) saveBkImage = std::make_unique<IMAGE>(w, h);
|
||
|
||
SetWorkingImage(nullptr); // ★抓屏幕
|
||
getimage(saveBkImage.get(), x, y, w, h);
|
||
hasSnap = true;
|
||
}
|
||
|
||
void Control::restBackground()
|
||
{
|
||
if (!hasSnap || !saveBkImage) return;
|
||
// 直接回贴屏幕(与抓取一致)
|
||
SetWorkingImage(nullptr);
|
||
putimage(saveBkX, saveBkY, saveBkImage.get());
|
||
}
|
||
|
||
void Control::discardBackground()
|
||
{
|
||
if (saveBkImage)
|
||
{
|
||
restBackground();
|
||
SX_LOGD("Snap") << SX_T("丢弃背景快照:id=","discardBackground: id=") << id << " hasSnap=" << (hasSnap ? 1 : 0);
|
||
saveBkImage.reset();
|
||
}
|
||
hasSnap = false; saveWidth = saveHeight = 0;
|
||
}
|
||
|
||
void Control::invalidateBackgroundSnapshot()
|
||
{
|
||
if (saveBkImage)
|
||
{
|
||
SX_LOGD("Snap") << SX_T("作废背景快照:id=", "invalidateBackgroundSnapshot: id=") << id
|
||
<< " hasSnap=" << (hasSnap ? 1 : 0);
|
||
saveBkImage.reset();
|
||
}
|
||
hasSnap = false;
|
||
saveBkX = saveBkY = 0;
|
||
saveWidth = saveHeight = 0;
|
||
}
|
||
|