refactor(panels): 面板静态特征收敛为唯一登记表 + 标记面板绘制分段(§17.6 第 1/2 项)

- 新增 pvdetail::PanelKindTraits + PanelTraitsOf(唯一 switch):标题 / 排版类别 /
  滚动字段 / 面板状态 / 功能开关同表登记;新增面板只改「枚举 + 一行」,
  -Wswitch 仍在唯一登记点生效。PanelTitle 改为「前缀 + rangeSuffix 标志」,
  不再依赖 switch(标题降级为数据,不再需要函数指针)。
- PaintPanelMarkerGroups(约 340 行)拆成 6 个具名函数,主函数只保留绘制顺序
  (普通分组栏 -> 条目 -> 表头 -> 吸顶栏 -> 滚动条 -> 透明度行);纯搬移、0 字节变化。
- 顺手修正该函数体遗留的多余缩进。

验证:release/debug/tools 零 warning;UnitTests 260/19;OffscreenChecks -all 7/133;
panels -mode compare 7 面板像素哈希逐项不变;rowgeom compare 965+7 / 1268+7 键 0 差异。
产物 781,824 -> 782,336 B(+512,全部来自登记表)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 17:22:06 +08:00
1 parent a9f1563896
commit 0c26da07d2
8 files changed
+761 -453

No files matched your search

+98 -108
View File
@@ -1096,99 +1096,21 @@ namespace jdi
// ⚠️ 这些 `switch` **不得写 `default:`** —— 让 `-Wswitch` 在新增
// `EdgePanelKind` 而漏登记时直接报警(编译期保护,0 运行时代价)。
/** @brief 取某种面板的 UI 状态(展开/折起/位置)——可写版本。 */
inline LanePanelState& LanePanelStateOf(Lane& lane, EdgePanelKind kind)
{
switch (kind)
{
case EdgePanelKind::COUNT:
return lane.edgePanel;
case EdgePanelKind::CENTER:
return lane.edgeCenterPanel;
case EdgePanelKind::MEAN:
return lane.meanFilterPanel;
case EdgePanelKind::PEAK:
return lane.weakPeakPanel;
case EdgePanelKind::MARK:
return lane.freeMarkerPanel;
case EdgePanelKind::RESAMPLE:
return lane.resamplePanel;
case EdgePanelKind::CLIP:
return lane.clipPanel;
}
// 枚举值越界(只可能来自非法强制转换):返回一个安全的占位对象。
return lane.edgePanel;
}
/** @brief 取某种面板的 UI 状态——只读版本。 */
inline const LanePanelState& LanePanelStateOf(const Lane& lane, EdgePanelKind kind)
{
return LanePanelStateOf(const_cast<Lane&>(lane), kind);
}
/** @brief 取某种面板的**内容滚动偏移**(客户区像素)——可写版本。 */
inline float& LanePanelScrollOf(Lane& lane, EdgePanelKind kind)
{
switch (kind)
{
case EdgePanelKind::COUNT:
return lane.edgeCountScroll;
case EdgePanelKind::CENTER:
return lane.edgeCenterScroll;
case EdgePanelKind::MEAN:
return lane.meanFilterScroll;
case EdgePanelKind::PEAK:
return lane.weakPeakScroll;
case EdgePanelKind::MARK:
return lane.freeMarkerScroll;
case EdgePanelKind::RESAMPLE:
return lane.resampleScroll;
case EdgePanelKind::CLIP:
return lane.clipScroll;
}
return lane.meanFilterScroll;
}
/** @brief 取某种面板的**内容滚动偏移**——只读版本。 */
inline const float& LanePanelScrollOf(const Lane& lane, EdgePanelKind kind)
{
return LanePanelScrollOf(const_cast<Lane&>(lane), kind);
}
/** @brief 该面板对应的功能是否已启用(右键菜单里的开关)。 */
inline bool LanePanelEnabled(const Lane& lane, EdgePanelKind kind)
{
switch (kind)
{
case EdgePanelKind::COUNT:
return lane.edgeCount;
case EdgePanelKind::CENTER:
return lane.edgeCenter;
case EdgePanelKind::MEAN:
return lane.meanFilter;
case EdgePanelKind::PEAK:
return lane.weakPeak;
case EdgePanelKind::MARK:
return lane.freeMarker;
case EdgePanelKind::RESAMPLE:
return lane.resample;
case EdgePanelKind::CLIP:
return lane.clip;
}
return false;
}
// ---- 面板的「排版类别」与「滚动几何」的唯一来源 --------------------------------
// ---- 面板的「排版类别」与「静态特征」的唯一来源 --------------------------------
//
// ⚠️⚠️ 为什么要有这一层:面板的**分类布尔量**过去在三个地方各写一份
// (`EdgePanelWidth` / `EdgePanelLayoutFor` / `PaintEdgeCursor`),
// 每份都是 6 个 `kind == XXX` + 3 条组合判断。新增一种面板时若只改其中两处,
// 就会出现"宽度按 A 类算、绘制按 B 类画"的错位 —— 这正是历史上
// 「元素超出浮动面板」「内容区多出一段空白」「滚动条画在表头上」这类缺陷的共同形态。
// 现在只在这里分派一次,三个消费者都读同一份结果。
// 现在只在这里登记一次,所有消费者都读同一份结果。
//
// ⚠️ 同样**不写 `default:`**:新增 `EdgePanelKind` 而未登记时,
// ⚠️ `PanelTraitsOf` 的 `switch` **不写 `default:`**:新增 `EdgePanelKind` 而未登记时,
// `-Wswitch`(属 `-Wall`)会直接编译报警。
//
// ⚠️ 标题里只有「边沿计数」「中心轴边沿计数」需要追加运行时后缀(`EdgeRangeSuffix()`
// 取决于双光标是否启用),因此表里存的是**标题前缀 + 一个标志**,
// 而不是 `std::wstring`(无法 constexpr)或函数指针(会把"数据"重新变成"代码")。
/**
* @brief 一种浮动面板的排版类别(互斥 + 若干可选部件)。
@@ -1225,40 +1147,108 @@ namespace jdi
bool footerButtons() const { return buildButton || twoButtons; }
};
/** @brief 取某种面板的排版类别(唯一分类来源)。 */
inline PanelStyle PanelStyleOf(EdgePanelKind kind)
/** @brief `PanelStyle` 的三个具名构造函数(聚合初始化顺序易错,这里给出语义化入口)。 */
inline constexpr PanelStyle ListPanelStyle(bool buildButton, bool twoButtons) noexcept
{
return PanelStyle{true, false, false, buildButton, twoButtons, false};
}
inline constexpr PanelStyle CountTablePanelStyle() noexcept
{
return PanelStyle{false, true, false, false, false, false};
}
inline constexpr PanelStyle MarkerPanelStyle() noexcept
{
return PanelStyle{false, false, true, false, false, true};
}
/**
* @brief 一种浮动面板的全部**静态特征**(排版类别 / 标题 / 三个字段偏移)。
*
* 新增一种面板时**只允许**改三处:`EdgePanelKind` 加一个枚举值、
* 本表加一行、`PanelTitle` 不需要动(标题就在这一行里)。
* 其余消费者(宽度 / 布局 / 绘制 / 滚动 / 命中 / 调试盒 / 缓存键)全部自动跟上。
*/
struct PanelKindTraits
{
PanelStyle style; /**< 排版类别与底部控件。 */
const wchar_t* title = L""; /**< 标题前缀(`rangeSuffix` 为真时再追加区间后缀)。 */
bool rangeSuffix = false; /**< 标题是否追加 `EdgeRangeSuffix()`(仅两种边沿计数)。 */
float Lane::*scroll = nullptr; /**< 内容滚动偏移。 */
LanePanelState Lane::*panel = nullptr; /**< 面板 UI 状态(展开/折起/位置)。 */
bool Lane::*enabled = nullptr; /**< 功能开关(右键菜单里的勾选项)。 */
};
/** @brief 取某种面板的全部静态特征(**唯一登记点**)。 */
inline constexpr PanelKindTraits PanelTraitsOf(EdgePanelKind kind)
{
PanelStyle style;
switch (kind)
{
case EdgePanelKind::COUNT:
style.countTable = true;
break;
return PanelKindTraits{CountTablePanelStyle(), L"边沿计数", true,
&Lane::edgeCountScroll, &Lane::edgePanel, &Lane::edgeCount};
case EdgePanelKind::CENTER:
style.listRows = true;
break;
return PanelKindTraits{ListPanelStyle(false, false), L"中心轴边沿计数", true,
&Lane::edgeCenterScroll, &Lane::edgeCenterPanel,
&Lane::edgeCenter};
case EdgePanelKind::MEAN:
style.listRows = true;
style.buildButton = true;
break;
return PanelKindTraits{ListPanelStyle(true, false), L"均值滤波", false,
&Lane::meanFilterScroll, &Lane::meanFilterPanel,
&Lane::meanFilter};
case EdgePanelKind::PEAK:
style.listRows = true;
style.buildButton = true;
break;
return PanelKindTraits{ListPanelStyle(true, false), kWeakPeakTitle, false,
&Lane::weakPeakScroll, &Lane::weakPeakPanel, &Lane::weakPeak};
case EdgePanelKind::MARK:
style.markerGroups = true;
style.markerFooter = true;
break;
return PanelKindTraits{MarkerPanelStyle(), L"自由标记", false,
&Lane::freeMarkerScroll, &Lane::freeMarkerPanel,
&Lane::freeMarker};
case EdgePanelKind::RESAMPLE:
style.listRows = true;
style.buildButton = true;
break;
return PanelKindTraits{ListPanelStyle(true, false), kResampleTitle, false,
&Lane::resampleScroll, &Lane::resamplePanel, &Lane::resample};
case EdgePanelKind::CLIP:
style.listRows = true;
style.twoButtons = true;
break;
return PanelKindTraits{ListPanelStyle(false, true), kClipTitle, false,
&Lane::clipScroll, &Lane::clipPanel, &Lane::clip};
}
return style;
// 枚举值越界(只可能来自非法强制转换):退到「均值滤波」这份,保证指针非空。
return PanelKindTraits{ListPanelStyle(true, false), L"", false, &Lane::meanFilterScroll,
&Lane::meanFilterPanel, &Lane::meanFilter};
}
/** @brief 取某种面板的排版类别(唯一分类来源)。 */
inline PanelStyle PanelStyleOf(EdgePanelKind kind)
{
return PanelTraitsOf(kind).style;
}
/** @brief 取某种面板的 UI 状态(展开/折起/位置)——可写版本。 */
inline LanePanelState& LanePanelStateOf(Lane& lane, EdgePanelKind kind)
{
return lane.*(PanelTraitsOf(kind).panel);
}
/** @brief 取某种面板的 UI 状态——只读版本。 */
inline const LanePanelState& LanePanelStateOf(const Lane& lane, EdgePanelKind kind)
{
return LanePanelStateOf(const_cast<Lane&>(lane), kind);
}
/** @brief 取某种面板的**内容滚动偏移**(客户区像素)——可写版本。 */
inline float& LanePanelScrollOf(Lane& lane, EdgePanelKind kind)
{
return lane.*(PanelTraitsOf(kind).scroll);
}
/** @brief 取某种面板的**内容滚动偏移**——只读版本。 */
inline const float& LanePanelScrollOf(const Lane& lane, EdgePanelKind kind)
{
return LanePanelScrollOf(const_cast<Lane&>(lane), kind);
}
/** @brief 该面板对应的功能是否已启用(右键菜单里的开关)。 */
inline bool LanePanelEnabled(const Lane& lane, EdgePanelKind kind)
{
return lane.*(PanelTraitsOf(kind).enabled);
}
/**