Refactor layout pipeline, add KEY5 regression, and fix tooltip hide

This commit is contained in:
Codex
2026-04-10 23:26:25 +08:00
parent 241a564095
commit b7ad960518
13 changed files with 719 additions and 290 deletions
+51 -184
View File
@@ -32,26 +32,54 @@ Canvas::Canvas(int x, int y, int width, int height)
this->id = "Canvas";
}
void Canvas::relayoutManagedChildren()
{
// Canvas 负责子控件从“父局部设计矩形”到“当前世界矩形”的转换。
// 当 Canvas 自己的位置或尺寸变化后,所有受它管理的子控件都要重新走一次统一解算。
for (auto& child : controls)
{
const StellarX::ResolvedLayoutRect rect =
child->resolveLayoutRect(localWidth, localHeight, x, y, width, height);
child->applyResolvedLayoutRect(rect);
}
}
void Canvas::setX(int x)
{
this->x = x;
for (auto& c : controls)
{
c->onWindowResize();
c->setX(c->getLocalX() + this->x);
}
dirty = true;
// 公开 setter 在 Canvas 上不能再视为“单纯改自己的 x”:
// 一旦容器移动,子控件的世界坐标也必须整体重算。
applyRuntimeRectDirect(x, y, width, height);
relayoutManagedChildren();
onWindowResize();
}
void Canvas::setY(int y)
{
this->y = y;
for (auto& c : controls)
{
c->onWindowResize();
c->setY(c->getLocalY() + this->y);
}
dirty = true;
applyRuntimeRectDirect(this->x, y, width, height);
relayoutManagedChildren();
onWindowResize();
}
void Canvas::setWidth(int width)
{
applyRuntimeRectDirect(x, y, width, height);
relayoutManagedChildren();
onWindowResize();
}
void Canvas::setHeight(int height)
{
applyRuntimeRectDirect(x, y, width, height);
relayoutManagedChildren();
onWindowResize();
}
void Canvas::applyResolvedLayoutRect(const StellarX::ResolvedLayoutRect& rect)
{
// 统一解算器已经给出当前运行态世界矩形;
// Canvas 在应用自身矩形后,还必须继续刷新全部子控件。
applyRuntimeRectDirect(rect.worldX, rect.worldY, rect.width, rect.height);
relayoutManagedChildren();
}
void Canvas::clearAllControls()
@@ -173,11 +201,12 @@ bool Canvas::handleEvent(const ExMessage& msg)
void Canvas::addControl(std::unique_ptr<Control> control)
{
//坐标转化
control->setX(control->getLocalX() + this->x);
control->setY(control->getLocalY() + this->y);
control->setParent(this);
// 新子控件加入容器时,立刻按“当前容器运行态矩形”解算一次,
// 避免后续第一次 draw / resize 前 world 坐标仍停留在设计态。
const StellarX::ResolvedLayoutRect rect =
control->resolveLayoutRect(localWidth, localHeight, this->x, this->y, this->width, this->height);
control->applyResolvedLayoutRect(rect);
SX_LOGI("Canvas")
<< SX_T("添加子控件:父=Canvas 子id=", "addControl: parent=Canvas childId=")
<< control->getId()
@@ -264,173 +293,11 @@ void Canvas::setDirty(bool dirty)
void Canvas::onWindowResize()
{
// 首先处理自身的快照等逻辑
// resize 语义已收口:
// Canvas 不再在这里重新解算布局,只负责丢快照、标脏,并向子控件传播“环境已变化”。
Control::onWindowResize();
// 记录父容器原始尺寸(用于计算子控件的右/下边距)
int origParentW = this->localWidth;
int origParentH = this->localHeight;
// 当前容器的新尺寸
int finalW = this->width;
int finalH = this->height;
// 当前容器的新坐标(全局坐标)
int parentX = this->x;
int parentY = this->y;
// 调整每个子控件在 AnchorToEdges 模式下的位置与尺寸
for (auto& ch : controls)
{
// Only adjust when using anchor-to-edges layout
if (ch->getLayoutMode() == StellarX::LayoutMode::AnchorToEdges)
{
// Determine whether this child is a Table; tables keep their height constant
bool isTable = (dynamic_cast<Table*>(ch.get()) != nullptr);
// Unpack anchors
auto a1 = ch->getAnchor_1();
auto a2 = ch->getAnchor_2();
bool anchorLeft = (a1 == StellarX::Anchor::Left || a2 == StellarX::Anchor::Left);
bool anchorRight = (a1 == StellarX::Anchor::Right || a2 == StellarX::Anchor::Right);
bool anchorTop = (a1 == StellarX::Anchor::Top || a2 == StellarX::Anchor::Top);
bool anchorBottom = (a1 == StellarX::Anchor::Bottom || a2 == StellarX::Anchor::Bottom);
// If it's a table, treat as anchored left and right horizontally and anchored top vertically by default.
if (isTable)
{
anchorLeft = true;
anchorRight = true;
// If no explicit vertical anchor was provided, default to top.
if (!(anchorTop || anchorBottom))
{
anchorTop = true;
}
}
// Compute new X and width
int newX = ch->getX();
int newWidth = ch->getWidth();
if (anchorLeft && anchorRight)
{
// Scale horizontally relative to parent's size.
if (origParentW > 0)
{
// Maintain proportional position and size based on original local values.
double scaleW = static_cast<double>(finalW) / static_cast<double>(origParentW);
newX = parentX + static_cast<int>(ch->getLocalX() * scaleW + 0.5);
newWidth = static_cast<int>(ch->getLocalWidth() * scaleW + 0.5);
}
else
{
// Fallback: keep original
newX = parentX + ch->getLocalX();
newWidth = ch->getLocalWidth();
}
}
else if (anchorLeft && !anchorRight)
{
// Only left anchored: keep original width and left margin.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
else if (!anchorLeft && anchorRight)
{
// Only right anchored: keep original width and right margin.
newWidth = ch->getLocalWidth();
int origRightDist = origParentW - (ch->getLocalX() + ch->getLocalWidth());
newX = parentX + finalW - origRightDist - newWidth;
}
else
{
// No horizontal anchor: position relative to parent's left and width unchanged.
newWidth = ch->getLocalWidth();
newX = parentX + ch->getLocalX();
}
ch->setX(newX);
ch->setWidth(newWidth);
// Compute new Y and height
int newY = ch->getY();
int newHeight = ch->getHeight();
if (isTable)
{
// Table: Height remains constant; adjust Y based on anchors.
newHeight = ch->getLocalHeight();
if (anchorTop && anchorBottom)
{
// If both top and bottom anchored, scale Y but keep height.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: default to top
newY = parentY + ch->getLocalY();
}
}
else
{
if (anchorTop && anchorBottom)
{
// Scale vertically relative to parent's size.
if (origParentH > 0)
{
double scaleH = static_cast<double>(finalH) / static_cast<double>(origParentH);
newY = parentY + static_cast<int>(ch->getLocalY() * scaleH + 0.5);
newHeight = static_cast<int>(ch->getLocalHeight() * scaleH + 0.5);
}
else
{
newY = parentY + ch->getLocalY();
newHeight = ch->getLocalHeight();
}
}
else if (anchorTop && !anchorBottom)
{
// Top anchored only: keep height constant
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
else if (!anchorTop && anchorBottom)
{
// Bottom anchored only: keep height and adjust Y relative to bottom
newHeight = ch->getLocalHeight();
int origBottomDist = origParentH - (ch->getLocalY() + ch->getLocalHeight());
newY = parentY + finalH - origBottomDist - newHeight;
}
else
{
// No vertical anchor: position relative to parent's top, height constant.
newHeight = ch->getLocalHeight();
newY = parentY + ch->getLocalY();
}
}
ch->setY(newY);
ch->setHeight(newHeight);
}
// Always forward the window resize event to the child (recursively).
ch->onWindowResize();
}
for (auto& child : controls)
child->onWindowResize();
}
void Canvas::requestRepaint(Control* parent)