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:
1 parent
a9f1563896
commit
0c26da07d2
8 files changed
+761
-453
No files matched your search
@@ -1201,7 +1201,8 @@ namespace jdi
|
||||
* @brief 设置某种面板的**内容滚动偏移**(客户区像素,0 = 顶部)—— 唯一实现。
|
||||
*
|
||||
* ⚠️ 下面七个 `SetLane*Scroll` 都只是本函数的一行转发(保留是为了不改调用点与原语义)。
|
||||
* 新增面板时**只需**在 `LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle` 三处登记,
|
||||
* 新增面板时**只需**在 `pvdetail::PanelTraitsOf`(`PlotViewInternal.hpp`)加一行:
|
||||
* 标题 / 排版类别 / 滚动字段 / 面板状态 / 功能开关都在那一行里,
|
||||
* 不要再复制一份"取字段 → 夹取 → 比较 → 写回"的实现。
|
||||
*
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
@@ -2574,6 +2575,39 @@ namespace jdi
|
||||
*/
|
||||
const std::vector<FreeMarkerContentEntry>& FreeMarkerContents(int laneIndex) const;
|
||||
|
||||
// ---- 「自由标记」面板内容绘制的**分段入口**(第 41 轮)-----------------------
|
||||
// `PaintPanelMarkerGroups` 只负责**顺序**(普通分组栏 → 条目 → 表头 → 吸顶分组栏 →
|
||||
// 滚动条 → 透明度滑块),各段的细节在这里。提取是纯搬移,
|
||||
// 判据 = `panels -mode compare`(像素哈希)+ `rowgeom -mode compare`(逐行几何)。
|
||||
// ⚠️ 顺序本身是硬约束:表头与吸顶分组栏必须画在条目**之后**,否则向上滚动的条目
|
||||
// 会盖住它们(这是原实现里唯一真正需要"读整段代码才看得见"的约束)。
|
||||
|
||||
/** @brief 把「自由标记」六列的实测文本宽度写回 `stored`(= `Lane::freeMarkerColumnWidths`)。 */
|
||||
void MeasureMarkerColumnWidths(const Lane& tabLane,
|
||||
const std::vector<FreeMarkerContentEntry>& contents,
|
||||
std::array<float, 6>& stored);
|
||||
|
||||
/** @brief 「自由标记」列分隔竖线(数据行与表头共用同一套列边界)。 */
|
||||
void PaintMarkerColumnSeparators(gd::Graphics& g, const EdgePanelLayout& layout, float y,
|
||||
float height);
|
||||
|
||||
/** @brief 「自由标记」一个信号分组栏(三角 + 名称 + `N 标记点` + ✕)。 */
|
||||
void PaintMarkerGroupBar(gd::Graphics& g, const EdgePanelLayout& layout, const Lane& tabLane,
|
||||
const EdgePanelLayout::MarkerGroup& group, std::size_t laneIndex);
|
||||
|
||||
/** @brief 「自由标记」一个固定点条目(六列 + 分隔线 + 行尾 ✕)。 */
|
||||
void PaintMarkerRow(gd::Graphics& g, const EdgePanelLayout& layout, const Lane& tabLane,
|
||||
const EdgePanelLayout::MarkerRow& markerRow,
|
||||
const FreeMarkerContentEntry& entry, std::size_t laneIndex);
|
||||
|
||||
/** @brief 「自由标记」一个展开分组的表头行(不透明底衬 + 六列标题)。 */
|
||||
void PaintMarkerTableHeader(gd::Graphics& g, const EdgePanelLayout& layout,
|
||||
const EdgePanelLayout::MarkerGroup& group);
|
||||
|
||||
/** @brief 「自由标记」底部「标记透明度」滑块行。 */
|
||||
void PaintMarkerOpacityRow(gd::Graphics& g, const EdgePanelLayout& layout, const Lane& tabLane,
|
||||
std::size_t laneIndex);
|
||||
|
||||
/** 按网格下标的「自由标记」内容缓存(`mutable`:只在绘制时按需重建)。 */
|
||||
mutable std::vector<FreeMarkerContentCache> m_freeMarkerContent;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user