refactor(panels): 浮动面板抽出唯一"面板特征层",消除 3 处分类/滚动几何/滚动设置的重复
背景(用户观察):7 个实验性功能都有自己的浮动面板,观感与骨架高度一致
(标题栏 = 折起三角 + GX 徽标 + 标题 + ✕,内容区可滚动 + 滚动条,底部按钮行),
但每新增一种面板或改动布局,总会出"意料之外"的缺陷。
审计结论:骨架绘制本来只有一份,真正的问题是**同一套"按种类分支"的代码被抄了多份**:
| 重复项 | 位置 | 份数 |
| --- | --- | --- |
| 分类布尔量(listStyle / countTable / hasButton…) | EdgePanelWidth / EdgePanelLayoutFor / PaintEdgeCursor | 3 |
| 滚动条几何 + 滚动量夹取 | 布局里表格 / 列表 / 自由标记三个分支 | 3 |
| 滚动条绘制(6 行) | 绘制里同样三个分支 | 3 |
| SetLane*Scroll 实现 | PlotViewFeatures.cpp | 7 |
| 按种类转发滚动写入 | 滚轮 / 拖滚动条 | 2 |
| 标题文本 | EdgePanelWidth 的三元式链 / 绘制里的三元式链 | 2(内容还不一致!) |
一、新增"面板特征层"(PlotViewInternal.hpp,pvdetail)
* `PanelStyle PanelStyleOf(EdgePanelKind)`:唯一分类来源,`listRows` / `countTable` /
`markerGroups` + `buildButton` / `twoButtons` / `markerFooter`,`switch` 穷举不写 default
(新增面板漏登记 ⇒ `-Wswitch` 编译报警)。
其中专门区分 `footerButtons()`(底部有按钮行)与 `buildButton`(是"构建为新信号")——
混为一谈会让「选区截取」底部少预留一行高度(本轮实测 124 → 87 px,被像素基线当场抓住)。
* `PanelScrollMetricsFor(...)`:滚动条轨道/滑块几何 + 滚动量夹取 + 内容宽度扣减的唯一实现。
* `PlotView::PanelTitle(EdgePanelKind)`:标题唯一来源。
⚠️ 修掉一处**潜伏**错误:`EdgePanelWidth` 的标题链漏了 MARK/CLIP(两者按"边沿计数"量宽),
目前两种标题恰好等宽所以看不出来,但任何文案改动都会让宽度与标题不匹配。
二、收敛调用点
* `EdgePanelWidth` / `EdgePanelLayoutFor`(表格 / 列表 / 自由标记三分支)/ `PaintEdgeCursor`
全部改用 `PanelStyleOf` 与 `PanelTitle`;滚动几何改用 `PanelScrollMetricsFor`。
* `EdgePanelLayoutFor` 的 5 层滚动量三元式 → `LanePanelScrollOf(lane, kind)`(该派发点第 33 轮
就已存在,但布局这里一直没用它)。
* `PaintEdgeCursor` 的滚动条绘制抽成 `paintPanelScrollBar(layout)`,三处调用。
* 7 个 `SetLane*Scroll` 改为一行转发到新的 `SetLanePanelScroll(laneIndex, kind, scroll)`;
滚轮与"拖滚动条"两条 `switch`/`if` 链改为直接调它(读与写从此同一个入口)。
三、新增离屏检查 `panelscroll`(护栏)
* 对 7 种面板逐一:可滚动时滚到底 / 滚回顶,断言 `scrollOffset` 夹取到 `maxScroll`、
滑块到达/返回轨道端点、内容区几何不随滚动改变;不可滚动时断言不出现滚动条。
* 用 `MF4-Record.MF4`(566 信号合并到同一网格)实测 6/7 面板可滚(自由标记无固定点,正确)。
没有这条检查,本次收敛就等于"改完只能靠肉眼"。
验证(本机 GCC 15.2.0 UCRT / 125% DPI)
* `panels -mode compare`:7 个面板的**像素哈希与几何逐项不变**(零差异)。
这条检查当场抓到了上面 `footerButtons` 的回归,证明基线有效。
* `OffscreenChecks -all`:6 个检查全 PASS,`checks=87 failed=0`。
* `UnitTests`:`checks=219 failed=0`。
* release 全量零 error / 零 warning;产物 788,480 → **787,968 B**(−512)。
This commit is contained in:
1 parent
5c48bf922e
commit
abcf3e6c44
7 files changed
+416
-250
No files matched your search
@@ -1197,6 +1197,20 @@ namespace jdi
|
||||
*/
|
||||
bool SetLaneFreeMarkerOpacity(int laneIndex, float opacity);
|
||||
|
||||
/**
|
||||
* @brief 设置某种面板的**内容滚动偏移**(客户区像素,0 = 顶部)—— 唯一实现。
|
||||
*
|
||||
* ⚠️ 下面七个 `SetLane*Scroll` 都只是本函数的一行转发(保留是为了不改调用点与原语义)。
|
||||
* 新增面板时**只需**在 `LanePanelScrollOf` / `PanelStyleOf` / `PanelTitle` 三处登记,
|
||||
* 不要再复制一份"取字段 → 夹取 → 比较 → 写回"的实现。
|
||||
*
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] kind 面板种类。
|
||||
* @param [IN] scroll 目标滚动量(负值按 0 处理)。
|
||||
* @return 偏移真的发生变化返回 true。
|
||||
*/
|
||||
bool SetLanePanelScroll(int laneIndex, EdgePanelKind kind, float scroll);
|
||||
|
||||
/**
|
||||
* @brief 设置某网格「自由标记」面板内容的滚动偏移(客户区像素,0 = 顶部)。
|
||||
* @return 偏移真的发生变化返回 true。
|
||||
@@ -2846,6 +2860,23 @@ namespace jdi
|
||||
double edgeValue, bool hasRange, double rangeMin, double rangeMax,
|
||||
std::size_t& rising, std::size_t& falling, bool& complete) const;
|
||||
|
||||
/**
|
||||
* @brief 浮动面板的**标题文本**(唯一来源:宽度计算与绘制共用)。
|
||||
*
|
||||
* ⚠️ 过去标题在两处各写一条多层三元式:
|
||||
* * `EdgePanelWidth` 里的链**漏了「自由标记」与「选区截取」**(两种面板都退化成
|
||||
* 「边沿计数」的文字来量宽度);
|
||||
* * `PaintEdgeCursor` 里的链是完整的。
|
||||
* 目前两种标题恰好与「边沿计数」等宽(或宽度被其它列主导),所以看不出来;
|
||||
* 但任何一次文案改动都会让"面板宽度与实际标题不匹配"(标题被省略 / 右侧留白)。
|
||||
*
|
||||
* 现在宽度与绘制都调本函数,且 `switch` 穷举、不写 `default:`。
|
||||
*
|
||||
* @param [IN] kind 面板种类。
|
||||
* @return 标题(「边沿计数」与「中心轴边沿计数」会追加双光标区间尾缀)。
|
||||
*/
|
||||
std::wstring PanelTitle(EdgePanelKind kind) const;
|
||||
|
||||
/**
|
||||
* @brief 计算实验性功能面板所需的宽度(排版与绘制共用,保证文字完整不被省略)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
|
||||
@@ -1178,6 +1178,142 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- 面板的「排版类别」与「滚动几何」的唯一来源 --------------------------------
|
||||
//
|
||||
// ⚠️⚠️ 为什么要有这一层:面板的**分类布尔量**过去在三个地方各写一份
|
||||
// (`EdgePanelWidth` / `EdgePanelLayoutFor` / `PaintEdgeCursor`),
|
||||
// 每份都是 6 个 `kind == XXX` + 3 条组合判断。新增一种面板时若只改其中两处,
|
||||
// 就会出现"宽度按 A 类算、绘制按 B 类画"的错位 —— 这正是历史上
|
||||
// 「元素超出浮动面板」「内容区多出一段空白」「滚动条画在表头上」这类缺陷的共同形态。
|
||||
// 现在只在这里分派一次,三个消费者都读同一份结果。
|
||||
//
|
||||
// ⚠️ 同样**不写 `default:`**:新增 `EdgePanelKind` 而未登记时,
|
||||
// `-Wswitch`(属 `-Wall`)会直接编译报警。
|
||||
|
||||
/**
|
||||
* @brief 一种浮动面板的排版类别(互斥 + 若干可选部件)。
|
||||
*
|
||||
* 三类互斥的内容排版:
|
||||
* * `listRows` —— 可滚动**分组列表**:条目栏(三角 + 名称 + 复选框)+ 展开后的参数行;
|
||||
* * `countTable` —— **表格**:固定表头(信号名 / 上升沿 / 下降沿)+ 可滚动行;
|
||||
* * `markerGroups` —— **分组栏 + 条目**:每个信号一个可折起分组栏 + 固定点条目 + 吸顶。
|
||||
*/
|
||||
struct PanelStyle
|
||||
{
|
||||
bool listRows = false; /**< 可滚动分组列表(均值 / 中心轴 / 弱峰 / 重采样 / 选区截取)。 */
|
||||
bool countTable = false; /**< 固定表头的表格(边沿计数)。 */
|
||||
bool markerGroups = false; /**< 分组栏 + 条目(自由标记)。 */
|
||||
|
||||
/** @brief 内容区是否可滚动(三类都是,保留字段以便未来出现「不可滚动」的面板)。 */
|
||||
bool scrollableContent() const { return listRows || countTable || markerGroups; }
|
||||
|
||||
/** @brief 底部是否有「构建为新信号」样式的单一按钮。 */
|
||||
bool buildButton = false;
|
||||
/** @brief 底部是否有**两个并排**按钮(选区截取)。 */
|
||||
bool twoButtons = false;
|
||||
/** @brief 底部是否有「自由标记」那组固定控件(透明度滑块 + 3 个切换按钮)。 */
|
||||
bool markerFooter = false;
|
||||
|
||||
/**
|
||||
* @brief 底部是否存在任何内容(按钮 / 滑块 / 开关)。
|
||||
*
|
||||
* ⚠️ 与 `buildButton` **不是同一个概念**:「选区截取」有两个并排按钮,
|
||||
* 但没有「构建为新信号」;而面板高度算式里的「按钮行高」必须两者都为真。
|
||||
* 曾经把两者当作一回事 ⇒ 截取面板底部少预留一行高度(124 → 87 px),
|
||||
* 内容区却仍按旧高度计算 ⇒ 面板高度与内容区不一致。
|
||||
*/
|
||||
bool footerButtons() const { return buildButton || twoButtons; }
|
||||
};
|
||||
|
||||
/** @brief 取某种面板的排版类别(唯一分类来源)。 */
|
||||
inline PanelStyle PanelStyleOf(EdgePanelKind kind)
|
||||
{
|
||||
PanelStyle style;
|
||||
switch (kind)
|
||||
{
|
||||
case EdgePanelKind::COUNT:
|
||||
style.countTable = true;
|
||||
break;
|
||||
case EdgePanelKind::CENTER:
|
||||
style.listRows = true;
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
style.listRows = true;
|
||||
style.buildButton = true;
|
||||
break;
|
||||
case EdgePanelKind::PEAK:
|
||||
style.listRows = true;
|
||||
style.buildButton = true;
|
||||
break;
|
||||
case EdgePanelKind::MARK:
|
||||
style.markerGroups = true;
|
||||
style.markerFooter = true;
|
||||
break;
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
style.listRows = true;
|
||||
style.buildButton = true;
|
||||
break;
|
||||
case EdgePanelKind::CLIP:
|
||||
style.listRows = true;
|
||||
style.twoButtons = true;
|
||||
break;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 浮动面板**内容区滚动条**的几何与滚动量(三个排版分支共用同一套算式)。
|
||||
*
|
||||
* 旧实现把这段算式在 `EdgePanelLayoutFor` 里抄了三遍(表格 / 列表 / 自由标记),
|
||||
* 三份的差异只在于"总内容高"和"滚动量来自哪个字段"。任何一次微调(比如把
|
||||
* 最小滑块高度 18 改成别的)都必须记得改三处 —— 典型的"漏一处就错位"。
|
||||
*
|
||||
* @param [IN] panelLeft 面板左边界(客户区)。
|
||||
* @param [IN] contentTop 内容区上边界。
|
||||
* @param [IN] viewHeight 内容区可视高度。
|
||||
* @param [IN] contentTotal 内容总高度(可能大于可视高度)。
|
||||
* @param [IN] scrollValue 期望滚动量(会被夹到 `[0, maxScroll]`)。
|
||||
* @param [IN] widthWithoutScroll 未扣滚动条时的内宽。
|
||||
* @param [IN] leftPad 面板左内边距。
|
||||
* @param [IN] scrollWidth 滚动条宽度(已乘缩放)。
|
||||
* @param [IN] scale DPI 缩放。
|
||||
* @return 滚动指标(`scrollable == false` 时 `track/thumb` 为空矩形、`innerWidth` 不扣减)。
|
||||
*/
|
||||
struct PanelScrollMetrics
|
||||
{
|
||||
bool scrollable = false; /**< 内容是否超高(需要滚动条)。 */
|
||||
float maxScroll = 0.0f; /**< 最大滚动量。 */
|
||||
float scroll = 0.0f; /**< 实际滚动量(已夹取)。 */
|
||||
float innerWidth = 0.0f; /**< 内容可用宽度(需要滚动条时已扣掉它)。 */
|
||||
gd::RectF track; /**< 滚动条轨道。 */
|
||||
gd::RectF thumb; /**< 滚动条滑块。 */
|
||||
};
|
||||
|
||||
inline PanelScrollMetrics PanelScrollMetricsFor(float panelLeft, float contentTop, float viewHeight,
|
||||
float contentTotal, float scrollValue,
|
||||
float widthWithoutScroll, float leftPad,
|
||||
float scrollWidth, float scale)
|
||||
{
|
||||
PanelScrollMetrics metrics;
|
||||
metrics.scrollable = contentTotal > viewHeight + 0.5f;
|
||||
metrics.maxScroll = std::max(0.0f, contentTotal - viewHeight);
|
||||
metrics.scroll = std::max(0.0f, std::min(scrollValue, metrics.maxScroll));
|
||||
metrics.innerWidth = std::max(
|
||||
0.0f, widthWithoutScroll - (metrics.scrollable ? (scrollWidth + 4.0f * scale) : 0.0f));
|
||||
if (!metrics.scrollable)
|
||||
{
|
||||
return metrics;
|
||||
}
|
||||
metrics.track = gd::RectF(panelLeft + leftPad + metrics.innerWidth + 4.0f * scale, contentTop,
|
||||
scrollWidth, viewHeight);
|
||||
const float thumbHeight = std::max(18.0f * scale, viewHeight * (viewHeight / contentTotal));
|
||||
const float thumbTop =
|
||||
contentTop + (viewHeight - thumbHeight) *
|
||||
((metrics.maxScroll > 0.0f) ? (metrics.scroll / metrics.maxScroll) : 0.0f);
|
||||
metrics.thumb = gd::RectF(metrics.track.X, thumbTop, scrollWidth, thumbHeight);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算一条派生曲线在给定采样窗口内的取值(同步路径与后台线程共用)。
|
||||
*
|
||||
|
||||
@@ -1778,7 +1778,16 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneFreeMarkerScroll(int laneIndex, float scroll)
|
||||
/**
|
||||
* @brief 设置某种面板的**内容滚动偏移**(唯一实现,公开包装都在下面转发到这里)。
|
||||
*
|
||||
* ⚠️ 过去这里是七个几乎逐字重复的函数(自由标记 / 均值滤波 / 重采样 / 选区截取 /
|
||||
* 中心轴 / 弱峰 / 边沿计数),唯一差别是要写的字段名;调用侧还有两条 `if`/`switch`
|
||||
* 链再按种类转发一次。新增面板时必须在**三个**地方登记,漏一处就出现
|
||||
* "滚轮能滚、拖滚动条滚不动"或反之。
|
||||
* 现在字段一律经 `LanePanelScrollOf(lane, kind)` 取(唯一派发点),本函数是唯一写入点。
|
||||
*/
|
||||
bool PlotView::SetLanePanelScroll(int laneIndex, EdgePanelKind kind, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
@@ -1786,31 +1795,26 @@ namespace jdi
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.freeMarkerScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.freeMarkerScroll = clamped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneMeanFilterScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.meanFilterScroll - clamped) < 0.5f)
|
||||
float& stored = LanePanelScrollOf(lane, kind);
|
||||
if (std::fabs(stored - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 滚动只影响面板内部像素,缓冲区/场景都不变(面板像素缓存按键失效即可)。
|
||||
lane.meanFilterScroll = clamped;
|
||||
stored = clamped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneFreeMarkerScroll(int laneIndex, float scroll)
|
||||
{
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::MARK, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneMeanFilterScroll(int laneIndex, float scroll)
|
||||
{
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::MEAN, scroll);
|
||||
}
|
||||
|
||||
// ============================ 「重采样」 ============================
|
||||
|
||||
bool PlotView::SignalDataInterval(const SignalRef& ref, double& maxInterval,
|
||||
@@ -1963,18 +1967,7 @@ namespace jdi
|
||||
|
||||
bool PlotView::SetLaneResampleScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.resampleScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.resampleScroll = clamped;
|
||||
return true;
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::RESAMPLE, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::TakePendingResampleBuild(int& laneIndex, std::vector<ResampleBuild>& items)
|
||||
@@ -2072,18 +2065,7 @@ namespace jdi
|
||||
|
||||
bool PlotView::SetLaneClipScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.clipScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.clipScroll = clamped;
|
||||
return true;
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::CLIP, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::TakePendingClipBuild(int& laneIndex, std::vector<ClipBuild>& items)
|
||||
@@ -2100,54 +2082,19 @@ namespace jdi
|
||||
}
|
||||
|
||||
|
||||
bool PlotView::SetLaneEdgeCenterScroll(int laneIndex, float scroll) {
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.edgeCenterScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 滚动只影响面板内部像素,缓冲区/场景都不变(面板像素缓存按键失效即可)。
|
||||
lane.edgeCenterScroll = clamped;
|
||||
return true;
|
||||
bool PlotView::SetLaneEdgeCenterScroll(int laneIndex, float scroll)
|
||||
{
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::CENTER, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneWeakPeakScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.weakPeakScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 滚动只影响面板内部像素,缓冲区/场景都不变(面板像素缓存按键失效即可)。
|
||||
lane.weakPeakScroll = clamped;
|
||||
return true;
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::PEAK, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneEdgeCountScroll(int laneIndex, float scroll)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const float clamped = std::max(0.0f, scroll);
|
||||
if (std::fabs(lane.edgeCountScroll - clamped) < 0.5f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 滚动只影响面板内部像素,缓冲区/场景都不变(面板像素缓存按键失效即可)。
|
||||
lane.edgeCountScroll = clamped;
|
||||
return true;
|
||||
return SetLanePanelScroll(laneIndex, EdgePanelKind::COUNT, scroll);
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneFreeMarkerGroup(int laneIndex, const SignalRef& ref)
|
||||
|
||||
@@ -1819,33 +1819,9 @@ namespace jdi
|
||||
layout.scrollTrack.Y + travel));
|
||||
const float ratio = (thumbTop - layout.scrollTrack.Y) / travel;
|
||||
const float target = ratio * maxScroll;
|
||||
bool scrolled = false;
|
||||
// ⚠️ 穷举所有面板种类且**不写 `default:`**:将来新增面板而忘登记时,
|
||||
// `-Wswitch`(属 `-Wall`)会直接报错,而不是静默落到其它面板上。
|
||||
// (「自由标记」在上面的独立分支处理,不会到达这里。)
|
||||
switch (m_meanDragKind)
|
||||
{
|
||||
case EdgePanelKind::COUNT:
|
||||
scrolled = SetLaneEdgeCountScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::CENTER:
|
||||
scrolled = SetLaneEdgeCenterScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
scrolled = SetLaneMeanFilterScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::PEAK:
|
||||
scrolled = SetLaneWeakPeakScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::MARK:
|
||||
break;
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
scrolled = SetLaneResampleScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::CLIP:
|
||||
scrolled = SetLaneClipScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
}
|
||||
// ⚠️ 唯一写入入口(`SetLanePanelScroll`):旧实现是一条 7 分支 `switch`,
|
||||
// 新增面板漏登记时拖动滚动条毫无反应(而滚轮那条链可能已经加上了)。
|
||||
const bool scrolled = SetLanePanelScroll(m_markerScrollDragLane, m_meanDragKind, target);
|
||||
if (scrolled)
|
||||
{
|
||||
m_dragMoved = true;
|
||||
@@ -3566,30 +3542,10 @@ namespace jdi
|
||||
const float current = LanePanelScrollOf(wheelLane, hoveredKind);
|
||||
const float next = current - static_cast<float>(notches * 40.0 * m_scale);
|
||||
const float target = std::max(0.0f, std::min(next, maxScroll));
|
||||
switch (hoveredKind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
SetLaneEdgeCenterScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::PEAK:
|
||||
SetLaneWeakPeakScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
SetLaneEdgeCountScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
SetLaneResampleScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::CLIP:
|
||||
SetLaneClipScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
SetLaneMeanFilterScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::MARK:
|
||||
// 「自由标记」由下面的独立分支处理(它的内容高度来自布局)。
|
||||
break;
|
||||
}
|
||||
// ⚠️ 写入也走**同一个**入口(`SetLanePanelScroll`):旧实现这里是
|
||||
// 一条 7 分支 `switch`,与上面的读取各记一份映射。两处不同步就会
|
||||
// "读到了 A 面板的偏移、写进了 B 面板"(滚动条跳回顶端 / 滚不动)。
|
||||
SetLanePanelScroll(panelLane, hoveredKind, target);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4892,19 +4892,30 @@ namespace jdi
|
||||
//(`baseLayer + 1 + 面板序数`,面板序数按 `m_panelOrder` 底 → 顶)。
|
||||
// 少了这一步,下层面板的文字框会记在比上层面板更低的层里被反复挖洞/穿透。
|
||||
UiSetTextLayer(panelTextLayer + static_cast<int>(panelOrdinal));
|
||||
// ⚠️ 排版类别只从唯一来源取(`PanelStyleOf`):过去宽度 / 布局 / 本函数各写一份
|
||||
// 6 个 `kind == XXX` + 3 条组合判断,新增面板漏改一处就错位(见该函数注释)。
|
||||
const PanelStyle panelStyle = PanelStyleOf(kind);
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
const bool freeMarker = (kind == EdgePanelKind::MARK);
|
||||
const bool resamplePanel = (kind == EdgePanelKind::RESAMPLE);
|
||||
const bool clipPanel = (kind == EdgePanelKind::CLIP);
|
||||
// ⚠️ 「均值滤波」「中心轴边沿计数」「弱峰调整」「重采样」「选区截取」= 可滚动的分组列表
|
||||
//(条目栏 + 展开后的参数行);
|
||||
// **「边沿计数」= 表格**(表头:信号名 / 上升沿计数 / 下降沿计数),单独一支。
|
||||
// 它们都**不能**再走旧的逐条目分支:那个分支没有任何裁切,滚动时会画到标题栏上。
|
||||
const bool listStyle = (meanFilter || centerAxis || weakPeak || resamplePanel || clipPanel);
|
||||
const bool countTable = (kind == EdgePanelKind::COUNT);
|
||||
const bool hasButton = (meanFilter || weakPeak || resamplePanel || clipPanel);
|
||||
const bool listStyle = panelStyle.listRows;
|
||||
const bool countTable = panelStyle.countTable;
|
||||
const bool hasButton = panelStyle.buildButton;
|
||||
// 内容区滚动条:三个排版分支(表格 / 列表 / 自由标记)共用同一段绘制。
|
||||
// ⚠️ 过去这段 6 行代码在三个分支里各抄一份,任何外观微调(颜色 / 圆角)都要改三处。
|
||||
const auto paintPanelScrollBar = [this, &g](const EdgePanelLayout& panelLayout) {
|
||||
if (panelLayout.scrollTrack.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UiFillRoundRect(g, panelLayout.scrollTrack, panelLayout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, panelLayout.scrollThumb, panelLayout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
};
|
||||
{
|
||||
if (index >= count)
|
||||
{
|
||||
@@ -5230,13 +5241,7 @@ namespace jdi
|
||||
g.Restore(tableSave);
|
||||
|
||||
// 内容滚动条(只在内容超高时出现;表头不参与滚动)。
|
||||
if (layout.scrollTrack.Width > 0.0f)
|
||||
{
|
||||
UiFillRoundRect(g, layout.scrollTrack, layout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, layout.scrollThumb, layout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
paintPanelScrollBar(layout);
|
||||
}
|
||||
|
||||
// 「均值滤波」面板:逐信号的**信号色叠底标题栏**(左侧展开三角 + 信号名 +
|
||||
@@ -5757,13 +5762,7 @@ namespace jdi
|
||||
// ⚠️ 必须在 `g.Restore(meanSave)` **之后**画:滚动条位于内容区右侧、在
|
||||
// 内容裁剪区之外,若在裁剪内画会被整条裁掉(用户 2026-10-30 报告
|
||||
// "全部启用后没有出现滚动条"的根因)。
|
||||
if (layout.scrollTrack.Width > 0.0f)
|
||||
{
|
||||
UiFillRoundRect(g, layout.scrollTrack, layout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, layout.scrollThumb, layout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
paintPanelScrollBar(layout);
|
||||
}
|
||||
// 「自由标记」面板:按信号分组的**分组栏**(信号色托底 + 三角 + 名称 +
|
||||
// 「N 标记点」+ ✕),下属固定点条目六列对齐(序号 / 取值 / 枚举 / 增量 / 时刻 / 间隔),
|
||||
@@ -6073,13 +6072,7 @@ namespace jdi
|
||||
g.Restore(contentState);
|
||||
|
||||
// ---- 内容滚动条(只在内容超高时出现;按钮与滑块不参与滚动)----
|
||||
if (layout.scrollTrack.Width > 0.0f)
|
||||
{
|
||||
UiFillRoundRect(g, layout.scrollTrack, layout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, layout.scrollThumb, layout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
paintPanelScrollBar(layout);
|
||||
|
||||
// ---- 「标记透明度」滑动条(固定在「自由标记点选取」按钮上方)----
|
||||
if (layout.opacityTrack.Width > 0.0f)
|
||||
|
||||
@@ -17,6 +17,29 @@
|
||||
|
||||
namespace jdi
|
||||
{
|
||||
std::wstring PlotView::PanelTitle(EdgePanelKind kind) const
|
||||
{
|
||||
// ⚠️ 穷举且不写 `default:`(`-Wswitch` 会在新增面板时报警)。
|
||||
switch (kind)
|
||||
{
|
||||
case EdgePanelKind::COUNT:
|
||||
return std::wstring(L"边沿计数") + EdgeRangeSuffix();
|
||||
case EdgePanelKind::CENTER:
|
||||
return std::wstring(L"中心轴边沿计数") + EdgeRangeSuffix();
|
||||
case EdgePanelKind::MEAN:
|
||||
return std::wstring(L"均值滤波");
|
||||
case EdgePanelKind::PEAK:
|
||||
return std::wstring(kWeakPeakTitle);
|
||||
case EdgePanelKind::MARK:
|
||||
return std::wstring(L"自由标记");
|
||||
case EdgePanelKind::RESAMPLE:
|
||||
return std::wstring(kResampleTitle);
|
||||
case EdgePanelKind::CLIP:
|
||||
return std::wstring(kClipTitle);
|
||||
}
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
float PlotView::EdgePanelWidth(int laneIndex, EdgePanelKind kind) const
|
||||
{
|
||||
if (m_tab == nullptr || m_fonts == nullptr || laneIndex < 0 ||
|
||||
@@ -25,28 +48,18 @@ namespace jdi
|
||||
return kEdgePanelWidth * m_scale;
|
||||
}
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
// ⚠️ 排版类别与标题都只从唯一来源取(见 `PanelStyleOf` / `PanelTitle`):
|
||||
// 过去这三份分类布尔量在宽度 / 布局 / 绘制里各写一遍,新增面板漏改一处就错位。
|
||||
const PanelStyle panelStyle = PanelStyleOf(kind);
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
const bool resamplePanel = (kind == EdgePanelKind::RESAMPLE);
|
||||
const bool clipPanel = (kind == EdgePanelKind::CLIP);
|
||||
// 「均值滤波」「中心轴边沿计数」「弱峰调整」「重采样」「选区截取」= 可滚动的分组列表
|
||||
//(信号色叠底条目栏 + 右侧复选框 + 展开后的参数行);
|
||||
// **「边沿计数」= 表格**(表头:信号名 / 上升沿计数 / 下降沿计数)。
|
||||
// ⚠️ 旧式「逐条目」排版(无滚动的名称+参数行)已彻底删除:上百个信号时它会算出
|
||||
// 上万像素高的面板,超出绘图区后**面板像素缓存一次都存不进去** ⇒ 每帧整块重画。
|
||||
const bool listStyle = (centerAxis || meanFilter || weakPeak || resamplePanel || clipPanel);
|
||||
const bool countTable = (kind == EdgePanelKind::COUNT);
|
||||
const bool listStyle = panelStyle.listRows;
|
||||
const bool countTable = panelStyle.countTable;
|
||||
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “弱峰调整”
|
||||
// + 光标区间尾缀)。
|
||||
const std::wstring title =
|
||||
meanFilter ? std::wstring(L"均值滤波")
|
||||
: (weakPeak ? std::wstring(kWeakPeakTitle)
|
||||
: (resamplePanel
|
||||
? std::wstring(kResampleTitle)
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") +
|
||||
EdgeRangeSuffix())));
|
||||
// 标题行 = “GX” 徽标 + 标题(+ 双光标区间尾缀)。
|
||||
const std::wstring title = PanelTitle(kind);
|
||||
float width = kEdgePanelPad * 2.0f * m_scale + kLaneBadgeWidth * m_scale +
|
||||
8.0f * m_scale + UiMeasureTextWidth(m_fonts->uiSmall, title) +
|
||||
kEdgePanelTitleHeight * m_scale;
|
||||
@@ -214,7 +227,7 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||
}
|
||||
|
||||
if (meanFilter || weakPeak || resamplePanel)
|
||||
if (panelStyle.buildButton)
|
||||
{
|
||||
// 底部按钮行 = 「构建为新信号」(均值滤波 / 弱峰调整 / 重采样有),按按钮文字实测宽度确定下限。
|
||||
const float buttonWidth =
|
||||
@@ -330,19 +343,16 @@ namespace jdi
|
||||
return layout;
|
||||
}
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
// ⚠️ 排版类别只从唯一来源取(`PanelStyleOf`)—— 过去宽度 / 布局 / 绘制各写一份。
|
||||
const PanelStyle panelStyle = PanelStyleOf(kind);
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
const bool freeMarker = (kind == EdgePanelKind::MARK);
|
||||
const bool resamplePanel = (kind == EdgePanelKind::RESAMPLE);
|
||||
const bool clipPanel = (kind == EdgePanelKind::CLIP);
|
||||
// 「均值滤波」「中心轴边沿计数」「弱峰调整」「重采样」「选区截取」共用同一套
|
||||
//**可滚动分组列表**排布(信号色叠底条目栏 + 展开后的参数行);
|
||||
//「边沿计数」自 2026-11-02 起走**表格**分支。
|
||||
//⚠️ 「选区截取」的条目**不可展开**(只需右侧复选框),因此只占一行。
|
||||
const bool listStyle = (meanFilter || centerAxis || weakPeak || resamplePanel || clipPanel);
|
||||
const bool countTable = (kind == EdgePanelKind::COUNT);
|
||||
const bool hasButton = (meanFilter || weakPeak || resamplePanel || clipPanel);
|
||||
const bool listStyle = panelStyle.listRows;
|
||||
const bool countTable = panelStyle.countTable;
|
||||
const bool hasButton = panelStyle.buildButton;
|
||||
// ⚠️ 面板的「启用 / 展开 / 折起」状态一律通过 `LanePanelEnabled` /
|
||||
// `LanePanelStateOf` 取值(见 `PlotViewInternal.hpp` 的说明):
|
||||
// 旧实现是三条 7 层嵌套三元式,新增面板时极容易漏改或写错括号。
|
||||
@@ -377,7 +387,8 @@ namespace jdi
|
||||
const float pad = kEdgePanelPad * m_scale;
|
||||
// 「中心轴边沿计数」「均值滤波」「弱峰调整」共用同一套**可滚动列表**排布,
|
||||
// 因此不再有独立的底部编辑行;「边沿计数」走独立的**表格**分支。
|
||||
const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
const float buttonHeight =
|
||||
panelStyle.footerButtons() ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
// 面板底部(内容区之下)占的纵向空间:有按钮时 = pad + 按钮 + pad,无按钮时只有 pad。
|
||||
// ⚠️⚠️ 「选区截取」的两个按钮(截取为新信号 / 原始信号截取)是**并排在同一行**的
|
||||
// (见下方的几何赋值),因此 footer 只能按**一行**算。旧实现按 `2 * 按钮高 + 6`
|
||||
@@ -660,30 +671,21 @@ namespace jdi
|
||||
const float scrollW = kFreeMarkerScrollWidth * m_scale;
|
||||
const float viewHeight =
|
||||
std::max(0.0f, height - titleHeight - pad - tableHeaderHeight - pad);
|
||||
const bool scrollable = meanContentHeight > viewHeight + 0.5f;
|
||||
const float maxScroll = std::max(0.0f, meanContentHeight - viewHeight);
|
||||
const float scroll = std::max(0.0f, std::min(lane.edgeCountScroll, maxScroll));
|
||||
const float innerWidth =
|
||||
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
|
||||
// 滚动几何走唯一实现(见 `PanelScrollMetricsFor`)。
|
||||
const PanelScrollMetrics scrollMetrics =
|
||||
PanelScrollMetricsFor(left, top + titleHeight + pad + tableHeaderHeight, viewHeight,
|
||||
meanContentHeight, LanePanelScrollOf(lane, kind), width - pad * 2.0f,
|
||||
pad, scrollW, m_scale);
|
||||
const float innerWidth = scrollMetrics.innerWidth;
|
||||
layout.tableHeader =
|
||||
gd::RectF(left + pad, top + titleHeight + pad, innerWidth, tableHeaderHeight);
|
||||
layout.content = gd::RectF(left + pad, layout.tableHeader.Y + tableHeaderHeight, innerWidth,
|
||||
viewHeight);
|
||||
layout.markerContentHeight = meanContentHeight;
|
||||
layout.scrollOffset = scroll;
|
||||
if (scrollable)
|
||||
{
|
||||
// 滚动条只占**内容区**(不覆盖固定的表头,表头永远干净可读)。
|
||||
layout.scrollTrack =
|
||||
gd::RectF(left + pad + innerWidth + 4.0f * m_scale, layout.content.Y, scrollW,
|
||||
viewHeight);
|
||||
const float thumbHeight =
|
||||
std::max(18.0f * m_scale, viewHeight * (viewHeight / meanContentHeight));
|
||||
const float thumbTop =
|
||||
layout.content.Y +
|
||||
(viewHeight - thumbHeight) * ((maxScroll > 0.0f) ? (scroll / maxScroll) : 0.0f);
|
||||
layout.scrollThumb = gd::RectF(layout.scrollTrack.X, thumbTop, scrollW, thumbHeight);
|
||||
}
|
||||
layout.scrollOffset = scrollMetrics.scroll;
|
||||
// 滚动条只占**内容区**(不覆盖固定的表头,表头永远干净可读)。
|
||||
layout.scrollTrack = scrollMetrics.track;
|
||||
layout.scrollThumb = scrollMetrics.thumb;
|
||||
// 三列:名称列吃掉剩余宽度,两列计数按内容宽度固定 —— 表头与数据行严格对齐。
|
||||
const float gap = kEdgePanelStatColumnGap * m_scale;
|
||||
const float contentLeft = layout.content.X;
|
||||
@@ -723,7 +725,7 @@ namespace jdi
|
||||
layout.tableColumns[1].width = risingCol;
|
||||
layout.tableColumns[2].left = layout.tableColumns[1].left + risingCol + gap;
|
||||
layout.tableColumns[2].width = fallingCol;
|
||||
float rowY = layout.content.Y - scroll;
|
||||
float rowY = layout.content.Y - scrollMetrics.scroll;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
@@ -792,31 +794,20 @@ namespace jdi
|
||||
// 条目区下方与按钮之间留出一片空白(用户 2026-11-03 两次报告)。
|
||||
const float viewHeight =
|
||||
std::max(0.0f, height - titleHeight - pad - bottomPad - footerHeight);
|
||||
const bool scrollable = meanContentHeight > viewHeight + 0.5f;
|
||||
const float maxScroll = std::max(0.0f, meanContentHeight - viewHeight);
|
||||
const float storedScroll = meanFilter ? lane.meanFilterScroll
|
||||
: (weakPeak ? lane.weakPeakScroll
|
||||
: (resamplePanel ? lane.resampleScroll
|
||||
: (clipPanel ? lane.clipScroll
|
||||
: lane.edgeCenterScroll)));
|
||||
const float scroll = std::max(0.0f, std::min(storedScroll, maxScroll));
|
||||
const float innerWidth =
|
||||
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
|
||||
// ⚠️ 滚动量一律走 `LanePanelScrollOf(lane, kind)`(唯一派发点):
|
||||
// 过去这里是一条 5 层三元式(均值/弱峰/重采样/截取/中心轴),
|
||||
// 而「边沿计数」不在其中 —— 它与 `LanePanelScrollOf` 各记一份映射,
|
||||
// 新增面板时极易只改一处(滚轮能滚、拖动滚动条滚不动之类的错位)。
|
||||
const PanelScrollMetrics scrollMetrics =
|
||||
PanelScrollMetricsFor(left, top + titleHeight + pad, viewHeight, meanContentHeight,
|
||||
LanePanelScrollOf(lane, kind), width - pad * 2.0f, pad, scrollW,
|
||||
m_scale);
|
||||
const float innerWidth = scrollMetrics.innerWidth;
|
||||
layout.content = gd::RectF(left + pad, top + titleHeight + pad, innerWidth, viewHeight);
|
||||
layout.markerContentHeight = meanContentHeight;
|
||||
layout.scrollOffset = scroll;
|
||||
if (scrollable)
|
||||
{
|
||||
layout.scrollTrack =
|
||||
gd::RectF(left + pad + innerWidth + 4.0f * m_scale, layout.content.Y, scrollW,
|
||||
viewHeight);
|
||||
const float thumbHeight =
|
||||
std::max(18.0f * m_scale, viewHeight * (viewHeight / meanContentHeight));
|
||||
const float thumbTop =
|
||||
layout.content.Y +
|
||||
(viewHeight - thumbHeight) * ((maxScroll > 0.0f) ? (scroll / maxScroll) : 0.0f);
|
||||
layout.scrollThumb = gd::RectF(layout.scrollTrack.X, thumbTop, scrollW, thumbHeight);
|
||||
}
|
||||
layout.scrollOffset = scrollMetrics.scroll;
|
||||
layout.scrollTrack = scrollMetrics.track;
|
||||
layout.scrollThumb = scrollMetrics.thumb;
|
||||
|
||||
// 两行滑块的列宽(与 `EdgePanelWidth` 同一组算式 ⇒ 布局与宽度不会漂移)。
|
||||
const float labelColumn =
|
||||
@@ -846,7 +837,7 @@ namespace jdi
|
||||
std::max(10.0f * m_scale,
|
||||
(resetLeft - resetGap - valueColumn - 6.0f * m_scale) - trackLeft);
|
||||
|
||||
float rowY = layout.content.Y - scroll;
|
||||
float rowY = layout.content.Y - scrollMetrics.scroll;
|
||||
// ⚠️ 「选区截取」在**未启用双光标**时不生成任何信号行:面板只显示一句居中提示
|
||||
// (用户 2026-11-03 明确要求:不应显示信号名条目、更不应能滚动)。
|
||||
const bool clipRowsVisible = !clipPanel || clipReadyHere;
|
||||
@@ -1065,26 +1056,18 @@ namespace jdi
|
||||
{
|
||||
totalContent += groupRow;
|
||||
}
|
||||
const bool scrollable = totalContent > viewHeight + 0.5f;
|
||||
const float maxScroll = std::max(0.0f, totalContent - viewHeight);
|
||||
const float scroll = std::max(0.0f, std::min(lane.freeMarkerScroll, maxScroll));
|
||||
const float innerWidth =
|
||||
std::max(0.0f, width - pad * 2.0f - (scrollable ? (scrollW + 4.0f * m_scale) : 0.0f));
|
||||
// 滚动几何走唯一实现(与表格 / 列表分支同一套算式)。
|
||||
const PanelScrollMetrics scrollMetrics =
|
||||
PanelScrollMetricsFor(left, top + titleHeight + pad, viewHeight, totalContent,
|
||||
LanePanelScrollOf(lane, kind), width - pad * 2.0f, pad, scrollW,
|
||||
m_scale);
|
||||
const float innerWidth = scrollMetrics.innerWidth;
|
||||
layout.content =
|
||||
gd::RectF(left + pad, top + titleHeight + pad, innerWidth, viewHeight);
|
||||
layout.markerContentHeight = totalContent;
|
||||
if (scrollable)
|
||||
{
|
||||
layout.scrollTrack =
|
||||
gd::RectF(left + pad + innerWidth + 4.0f * m_scale, layout.content.Y, scrollW,
|
||||
viewHeight);
|
||||
const float thumbHeight =
|
||||
std::max(18.0f * m_scale, viewHeight * (viewHeight / totalContent));
|
||||
const float thumbTop =
|
||||
layout.content.Y +
|
||||
(viewHeight - thumbHeight) * ((maxScroll > 0.0f) ? (scroll / maxScroll) : 0.0f);
|
||||
layout.scrollThumb = gd::RectF(layout.scrollTrack.X, thumbTop, scrollW, thumbHeight);
|
||||
}
|
||||
layout.scrollTrack = scrollMetrics.track;
|
||||
layout.scrollThumb = scrollMetrics.thumb;
|
||||
const float scroll = scrollMetrics.scroll;
|
||||
|
||||
// 六列(① 序号 ② 标记值 ③ 枚举文本 ④ 增量 ⑤ 时间点 ⑥ 时间间隔)
|
||||
// 从左往右排布:每一行的同类信息都从同一横坐标开始 ⇒ 纵向上严格对齐(用户要求)。
|
||||
|
||||
Reference in new issue
Block a user