自由标记:面板表头/吸顶/行选中与悬停联动 + 原位显示 + 探测容差与阶跃吸附

- 面板:每个展开分组下方新增一行列表头(标记值/枚举文本/增量/时间点/时间间隔),
  与数据行共用列边界;列宽同时容纳列标题;表头行计入内容总高与面板高度;
  全部无枚举文本时仍整列缺省
- 吸顶:分组栏与表头各自按 max(自然, min(吸顶, 组下边界-自身高)) 钉住,
  组末尾按"表头先走 -> 条目 -> 分组栏最后离场";绘制顺序改为条目->表头->吸顶分组栏,
  被盖住的条目不绘制也不可命中
- 行交互:Lane::freeMarkerSelected(单击选中/再点取消)+ 独立的整行悬停状态
  (整行悬停不会把该行 ✕ 变成 danger 色);悬停时网格内对应固定点整体高亮
  (数据点圆环 + 胶囊强制不透明 + accent 描边,仅视界内)
- 固定点恢复原位显示(删除纵向避让,允许遮挡,回到初版方案)
- 探测判据重写:鼠标到画出来的折线的垂直距离 <= kSignalHitTolerance(与曲线高亮同源),
  命中后吸附到最近采样时刻 => 修复阶跃竖直跳变线无法探测/固定的问题;
  删除已无用的 kFreeMarkerHoverTolerance
- 修复滚动时条目文字串到面板标题栏(GDI 文本不认 GDI+ 裁剪,改用 UiTextClipped)
- 顺带清理过期注释(四列->五列、折起指示器已无横线、探测判据说明),
  RemoveFreeMarker 改为复用 RemoveFreeMarkerAt(选中下标校正只有一处)
- 验证:新增离屏 .preflight/mark4check 38/38(三组 A/B 均如实失败);
  回归 markcheck 61/61、fixcheck 37/37、round3check 31/31、round4check 34/34、
  round5check 5/5、editcheck 20/20、convcheck 18/18;make release/debug 零警告
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-22 21:04:03 +08:00
1 parent 8f735d51be
commit 79eebc281e
10 files changed
+618 -116

No files matched your search

+43 -3
View File
@@ -991,6 +991,16 @@ namespace jdi
*/
bool ToggleLaneFreeMarkerGroup(int laneIndex, const SignalRef& ref);
/**
* @brief 选中「自由标记」面板里的某个条目(`-1` = 取消选中)。
*
* 选中的行在面板里高亮;它与网格内的固定点一一对应。
* @param [IN] laneIndex 网格下标。
* @param [IN] itemIndex `Lane::freeMarkerItems` 下标(越界会被夹成“取消选中”)。
* @return 选中状态发生变化返回 true。
*/
bool SelectLaneFreeMarker(int laneIndex, int itemIndex);
/**
* @brief 切换某个实验性功能浮动面板的**折起**状态(只显示标题栏)。
* @param [IN] laneIndex 网格下标。
@@ -1716,9 +1726,25 @@ namespace jdi
{
SignalRef ref;
gd::RectF header; /**< 分组栏托底矩形。 */
/**
* @brief 分组栏下方的一行**列表头**(取值 / 枚举文本 / 增量 / 时间点 / 时间间隔)。
*
* 与条目列共用 `markerColumns` 的列边界 ⇒ 表头与数据严格对齐。
* 分组被收起(或还没登记)时为空矩形。
* ⚠️ 滚动时它可能与分组栏一起**悬浮**在内容区顶部(吸顶),
* 因此它的 Y 不一定是“分组栏 Y + 行高”。
*/
gd::RectF tableHeader;
gd::RectF triangle; /**< 展开/收起三角的命中矩形。 */
gd::RectF close; /**< 右侧✕的命中矩形。 */
bool collapsed = false;
/**
* @brief 该分组栏是否处于**吸顶**(滚动时被钉在内容区顶部)。
*
* 吸顶时分组栏与表头会盖住正在向上滚的条目 ⇒ 绘制侧必须把它们画在**条目之后**
* (否则会被条目覆盖),交互侧也已把这些被盖住的条目从 `markerRows` 里剔除。
*/
bool pinned = false;
};
/** 「自由标记」:信号分组栏(顺序与面板显示顺序一致)。 */
std::vector<MarkerGroup> markerGroups;
@@ -1788,9 +1814,13 @@ namespace jdi
* 该网格内所有可见信号中**离鼠标最近**的那一个。
*
* 两种模式由 `Lane::freeMarkerCursor` 决定:
* * **默认(非光标模式)**:只有鼠标**贴近某条信号曲线**(或该信号命中「自动点线」
* 稀疏判定、且鼠标靠近某个数据点)时才有结果(`valid == true`),也**不画**纵向光标;
* * **光标模式**:网格内始终 `valid == true`(画纵向光标),能取到数据点时再标注取值。
* * **默认(非光标模式)**:只有鼠标**贴近某条信号曲线**时才有结果(`valid == true`),
* 也**不画**纵向光标。⚠️ 判据 = 鼠标到**画出来的折线**的垂直距离 ≤
* `kSignalHitTolerance`(与「常规状态下触发信号曲线高亮」**完全同一范围**;
* 因此阶跃的竖直跳变段也能探测到)。判定通过后,标注时刻会被吸附到最近的**采样时刻**
* (固定点必须落在真实数据点上);
* * **光标模式**:网格内始终 `valid == true`(画纵向光标),能取到数据点时再标注取值
* (此时只有命中「自动点线」稀疏判据的信号才吸附)。
*/
struct FreeMarkerProbe
{
@@ -2665,6 +2695,16 @@ namespace jdi
/** 最近一次解析出的「自由标记」跟随数据点(绘制与命中共用)。 */
FreeMarkerProbe m_freeMarker;
/**
* @brief 上一帧鼠标悬停在「自由标记」面板的哪个**条目行**上(-1 = 没有)。
*
* 与 `m_freeMarkerPanelHoverLane/Item`(只表示 ✕ 的悬停)**分开**:
* 悬停在整行上时要把网格内对应的固定点整体高亮(取值胶囊 + 数据点),
* 但**不能**因此把该行的 ✕ 画成 danger 色(那是“准备删除”的语义)。
*/
int m_freeMarkerRowHoverLane = -1;
std::size_t m_freeMarkerRowHoverItem = static_cast<std::size_t>(-1);
/**
* @brief 上一次绘制时登记的固定点 ✕ 命中矩形。
*