Finalize layout stage 2 fixes and refresh regression scenes
This commit is contained in:
+219
-64
@@ -1,6 +1,25 @@
|
||||
#include "TabControl.h"
|
||||
#include "SxLog.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()
|
||||
{
|
||||
if (controls.empty())return;
|
||||
@@ -65,9 +84,9 @@ inline void TabControl::initTabPage()
|
||||
// TabControl 内部页签页仍然保留专用布局:
|
||||
// 这里负责把“当前选项卡容器矩形”拆分成页签栏和页面区,
|
||||
// 不把这部分细节下放到通用布局解算器里。
|
||||
//子控件坐标原点
|
||||
int nX = 0;
|
||||
int nY = 0;
|
||||
// 但页面内容子树的世界坐标映射职责,继续归页面 Canvas 自己管理。
|
||||
// 因此这里不再手工遍历页内子控件做 setX/setY,
|
||||
// 只安置每个页面 Canvas 的矩形,让 Canvas 通过自身 relayoutManagedChildren() 收口。
|
||||
switch (this->tabPlacement)
|
||||
{
|
||||
case StellarX::TabPlacement::Top:
|
||||
@@ -78,16 +97,6 @@ inline void TabControl::initTabPage()
|
||||
c.second->setWidth(this->width);
|
||||
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;
|
||||
case StellarX::TabPlacement::Bottom:
|
||||
for (auto& c : controls)
|
||||
@@ -97,16 +106,6 @@ inline void TabControl::initTabPage()
|
||||
c.second->setWidth(this->width);
|
||||
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;
|
||||
case StellarX::TabPlacement::Left:
|
||||
for (auto& c : controls)
|
||||
@@ -116,16 +115,6 @@ inline void TabControl::initTabPage()
|
||||
c.second->setWidth(this->width - tabBarHeight);
|
||||
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;
|
||||
case StellarX::TabPlacement::Right:
|
||||
for (auto& c : controls)
|
||||
@@ -135,16 +124,6 @@ inline void TabControl::initTabPage()
|
||||
c.second->setWidth(this->width - tabBarHeight);
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
@@ -163,12 +142,18 @@ void TabControl::refreshRuntimeLayout()
|
||||
TabControl::TabControl() :Canvas()
|
||||
{
|
||||
this->id = "TabControl";
|
||||
// TabControl 作为外层容器,当前阶段显式允许双轴 Stretch;
|
||||
// 内部页签栏和页面区仍由自己的专用布局逻辑管理。
|
||||
this->layoutCapability.allowStretchX = true;
|
||||
this->layoutCapability.allowStretchY = true;
|
||||
}
|
||||
|
||||
TabControl::TabControl(int x, int y, int width, int height)
|
||||
: Canvas(x, y, width, height)
|
||||
{
|
||||
this->id = "TabControl";
|
||||
this->layoutCapability.allowStretchX = true;
|
||||
this->layoutCapability.allowStretchY = true;
|
||||
}
|
||||
|
||||
TabControl::~TabControl()
|
||||
@@ -218,13 +203,13 @@ void TabControl::draw()
|
||||
Canvas::draw();
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.first->setDirty(true);
|
||||
c.first->draw();
|
||||
c.second->setDirty(true);
|
||||
c.second->draw();
|
||||
}
|
||||
for (auto& c : controls)
|
||||
{
|
||||
c.second->setDirty(true);
|
||||
c.second->draw();
|
||||
c.first->setDirty(true);
|
||||
c.first->draw();
|
||||
}
|
||||
|
||||
// 首次绘制时处理默认激活页签
|
||||
@@ -242,28 +227,90 @@ void TabControl::draw()
|
||||
bool TabControl::handleEvent(const ExMessage& msg)
|
||||
{
|
||||
if (!show)return false;
|
||||
resetEventVisualChanged();
|
||||
bool consume = false;
|
||||
for (auto& c : controls)
|
||||
if (c.first->handleEvent(msg))
|
||||
{
|
||||
consume = true;
|
||||
break;
|
||||
}
|
||||
if (!consume)
|
||||
bool anyVisualChanged = false;
|
||||
|
||||
// TabControl 的同层页签按钮/页面区事件分发顺序,
|
||||
// 与 Window / Canvas 保持一致:按倒序处理,后加入者视为更靠上层。
|
||||
if (msg.message == WM_MOUSEMOVE)
|
||||
{
|
||||
for (auto& c : controls)
|
||||
if (c.second->IsVisible())
|
||||
if (c.second->handleEvent(msg))
|
||||
for (auto it = controls.rbegin(); it != controls.rend(); ++it)
|
||||
{
|
||||
if (!consume)
|
||||
{
|
||||
if (it->first->handleEvent(msg))
|
||||
{
|
||||
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)
|
||||
requestRepaint(parent);
|
||||
markEventVisualChanged(anyVisualChanged || dirty);
|
||||
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)
|
||||
{
|
||||
controls.push_back(std::move(control));
|
||||
@@ -399,8 +446,15 @@ void TabControl::setActiveIndex(int idx)
|
||||
defaultActivation = idx;
|
||||
else
|
||||
{
|
||||
// 外部重复激活“已经处于激活状态”的页签时,不应再把整条 onToggleOn 链重跑一遍。
|
||||
// 否则当前可见页面会重复执行 onWindowResize()/setIsVisible(true),
|
||||
// 对页内像 Table 这种会越出页面边界绘制的控件,容易把快照链再次扰乱,留下残影。
|
||||
if (idx >= 0 && idx < controls.size())
|
||||
{
|
||||
if (getActiveIndex() == idx)
|
||||
return;
|
||||
controls[idx].first->setButtonClick(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,17 +498,118 @@ void TabControl::requestRepaint(Control* 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)
|
||||
{
|
||||
if (control.first->isDirty() && control.first->IsVisible())
|
||||
control.first->draw();
|
||||
if (control.second->isDirty() && control.second->IsVisible())
|
||||
control.second->draw();
|
||||
Control* page = control.second.get();
|
||||
if (!page->IsVisible())
|
||||
continue;
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user