全局 Y 轴自适应跟随网格结构 + 组名悬浮 + 稀疏信号默认点线 + 单点光标吸附 + 放大自动点线
一、全局「Y 轴自适应」跟随网格结构
* 新勾选信号产生的新网格现在会**继承**该开关:RebuildLanes 重建前先算
previousAllAutoFit(非空且每个网格都开着 = 全局开启的正式定义),LaneCarry 加
matched 标记,新出现的信号用它作为 fitYToSelection 的默认值。
* 新增 PlotView::ReapplyGlobalAutoFitIfEnabled(),在 Layout()(网格数量变化 →
Relayout)、RefreshLanes()(信号数量变化)、SplitLane() 之后立即重算一次。
⇒ 新勾选(按当前视界自适应)、多信号网格内取消显示某信号(剩余信号重新收敛)、
拆分网格(每份各自自适应)三项都覆盖。
二、信号列表「组名悬浮」
* 分组行被滚出列表上边界、而该组仍有信号留在列表里时,把分组行钉在列表顶部
(不透明底 + 底部边界线);整组翻过去后本组组名消失,改由下一组悬浮。
* 新增 SignalPanel::StickyGroupRowIndex() / PaintStickyGroupRow();RowRect 增加
m_rowYOverrideActive/m_rowYOverrideY 覆盖行顶部坐标(RowRect 是行内名称框 / ✕ /
徽标的唯一几何来源)。常规行循环跳过被钉住的那一行,避免同一行被画两次。
三、「少数数据点」信号的默认样式
* 有效采样点 1 个 → 「点线」+ 最大线宽(10px);2 … 16 个 → 「点线」+ 2px;
其余保持「线段」+ 1px。只在新建信号时生效(BuildSignalGroup 与
InsertDerivedSignal),不覆盖用户设置。
* 线宽范围常量统一到 DataModel.hpp(kSignalLineWidthMin/Max),SignalPanel 的线宽
滑块改为引用它们(原本两处各自硬编码,容易漂移)。
四、单点信号的光标吸附(吸附而非卡住)
* 旧逻辑要求时间轴至少 2 个采样点才能算「采样间距」,单点信号因此永远不吸附。
* 新增 SignalData::singlePointTime(加载时与 ComputeStats 同趟扫描缓存该点时刻),
SnapTimeToSample 新增半径分支(kCursorSnapPointRadiusPixels = 8 逻辑像素):
半径内吸附到该点,半径外自由移动(移开即自由,不是把光标吸住)。
五、放大到「采样点稀疏」时自动启用点线
* SignalStyle 新增运行态三字段:autoScatterActive / autoScatterSavedMode /
autoScatterUserOverride。
* AxisSparseInView()(采样点间距 > 半个绘图区宽度,与光标吸附上门限同源;单点时间轴
不算稀疏)+ UpdateAutoScatterStyles():进入阈值保存原绘制方式并切「点线」,
退出阈值(如适应视图)恢复;平移不改变缩放等级 ⇒ 结果稳定,不会样式抖动。
* 自动启用期间用户手动改了绘制方式则退出时**不恢复**(尊重用户选择);该判定必须在
恢复判断之前完成 —— 用户改完样式可能紧接着就退出阈值,否则会被覆盖。
* 钩子:SetTimeView(视界唯一写入口)与 Layout / RefreshLanes / SplitLane。
六、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
65/65 通过(连跑 3 次稳定):[A1]/[A2]/[A3] 全局自适应、[B] 组名悬浮(含钉住行与自然
位置逐像素一致、整组翻过后组名消失)、[C] 稀疏默认样式边界(1/2/5/16/17/1000 点)、
[D] 单点吸附(半径内吸附、半径外自由、无残留粘滞)、[E] 自动点线(进入/退出/平移稳定/
用户手动改过后不覆盖/再次进入再启用)。
A/B:临时禁用六个机制后重跑 ⇒ 21 项如实失败(覆盖全部六项),证明断言有判别力。
make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。
七、规则同步
Documents/agent_mem.md 新增 §9.29、§11.2 第 55–58 条硬约束与变更日志。
This commit is contained in:
1 parent
3bca3b1201
commit
9f6de3bba4
7 files changed
+559
-5
No files matched your search
@@ -439,6 +439,30 @@ namespace jdi
|
||||
*/
|
||||
void PaintSignalRow(gd::Graphics& g, int row);
|
||||
|
||||
/**
|
||||
* @brief 判断哪个分组行需要「悬浮」在列表顶部(返回行下标,无需悬浮时返回 -1)。
|
||||
*
|
||||
* 判据:分组行已被滚出列表上边界(`RowRect(row).Y < m_listRect.Y`),
|
||||
* 但该组内**仍有行留在列表里**(本组最后一行的下边界尚未滚过去)。
|
||||
* 两者同时成立时组名必须钉在最上方;整组都滚过去后组名跟随一起消失。
|
||||
*
|
||||
* @param [IN] rowCount 行总数。
|
||||
* @return 需要悬浮的分组行下标;无需悬浮返回 -1。
|
||||
*/
|
||||
int StickyGroupRowIndex(int rowCount) const;
|
||||
|
||||
/**
|
||||
* @brief 把分组行「钉在列表顶部」绘制(组名跟随滚动,直到整组滚过去)。
|
||||
*
|
||||
* 用户要求长列表(例如上百个信号的 MF4)滚动时组名一直可见。
|
||||
* ⚠️ 必须在常规行循环**之后**调用(它要盖在信号行之上),并且常规循环要**跳过**
|
||||
* `row` 行,否则该行会被画两次。
|
||||
*
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] row 需要悬浮的分组行下标(< 0 表示什么都不做)。
|
||||
*/
|
||||
void PaintStickyGroupRow(gd::Graphics& g, int row);
|
||||
|
||||
/**
|
||||
* @brief 在列表区内绘制行内文本(**显式裁剪到列表矩形**)。
|
||||
*
|
||||
@@ -678,6 +702,16 @@ namespace jdi
|
||||
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
|
||||
|
||||
float m_scrollOffset = 0.0f; /**< 列表滚动偏移量。 */
|
||||
/**
|
||||
* @brief 「组名悬浮」绘制用的行顶部坐标覆盖。
|
||||
*
|
||||
* 置位时 `RowRect` 忽略滚动偏移、直接返回 `m_rowYOverrideY` 作为行顶部,
|
||||
* 从而使分组行(以及它派生的名称框 / ✕ / 徽标)整体画在钉住的位置。
|
||||
* 只在使用期间短暂置位,绘制完立即复位(见 `PaintStickyGroupRow`)。
|
||||
*/
|
||||
bool m_rowYOverrideActive = false;
|
||||
float m_rowYOverrideY = 0.0f;
|
||||
|
||||
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
|
||||
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
|
||||
bool m_draggingOpacity = false; /**< 是否正在拖拽不透明度滑块。 */
|
||||
|
||||
@@ -114,8 +114,10 @@ namespace jdi
|
||||
const int kAdjustAbsolute = 2; /**< 绝对值。 */
|
||||
const int kAdjustCount = 3; /**< 按钮数量。 */
|
||||
|
||||
const float kWidthMin = 0.5f; /**< 允许的最小线宽(px)。 */
|
||||
const float kWidthMax = 10.0f; /**< 允许的最大线宽(px)。 */
|
||||
// 线宽范围与「稀疏信号默认线宽」共用 `DataModel.hpp` 的常量,避免两处漂移
|
||||
//(稀疏信号初始化时用的最大线宽必须与这里滑块的上限完全一致)。
|
||||
const float kWidthMin = kSignalLineWidthMin; /**< 允许的最小线宽(px)。 */
|
||||
const float kWidthMax = kSignalLineWidthMax; /**< 允许的最大线宽(px)。 */
|
||||
|
||||
/** @brief 系数调整允许的绝对值上限(防止输入极端值把波形完全挤出可视区)。 */
|
||||
const double kGainLimit = 10000.0;
|
||||
@@ -561,6 +563,15 @@ namespace jdi
|
||||
return gd::RectF();
|
||||
}
|
||||
|
||||
// 「组名悬浮」期间把分组行钉在列表顶部绘制(见 `PaintStickyGroupRow`):
|
||||
// 本函数是行内所有派生的唯一几何来源,因此只需在这里覆盖顶部坐标,
|
||||
// 名称框 / ✕ / 徽标等位置会自动跟随。
|
||||
if (m_rowYOverrideActive)
|
||||
{
|
||||
return gd::RectF(m_listRect.X, m_rowYOverrideY, m_listRect.Width,
|
||||
m_rows[static_cast<std::size_t>(row)].height);
|
||||
}
|
||||
|
||||
float top = m_listRect.Y - m_scrollOffset;
|
||||
for (int index = 0; index < row; ++index)
|
||||
{
|
||||
@@ -569,6 +580,70 @@ namespace jdi
|
||||
return gd::RectF(m_listRect.X, top, m_listRect.Width, m_rows[static_cast<std::size_t>(row)].height);
|
||||
}
|
||||
|
||||
int SignalPanel::StickyGroupRowIndex(int rowCount) const
|
||||
{
|
||||
// 「组名悬浮」:分组行被滚出列表上边界、而该组内仍有行留在列表里时,
|
||||
// 把分组行钉在列表顶部,直到该组全部行都滚过去为止。
|
||||
// 用户要求:组名要一直可见,方便在长列表(例如上百个信号的 MF4)里辨认当前分组。
|
||||
if (m_rowYOverrideActive || m_listRect.Height <= 0.0f)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
if (m_rows[static_cast<std::size_t>(row)].kind != RowKind::GROUP)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (RowRect(row).Y >= m_listRect.Y)
|
||||
{
|
||||
// 分组行本身仍在列表内(或更靠下):无需悬浮。
|
||||
continue;
|
||||
}
|
||||
|
||||
// 本组的最后一行(列表行按分组连续排列)。
|
||||
int last = row;
|
||||
for (int next = row + 1; next < rowCount; ++next)
|
||||
{
|
||||
if (m_rows[static_cast<std::size_t>(next)].kind == RowKind::GROUP)
|
||||
{
|
||||
break;
|
||||
}
|
||||
last = next;
|
||||
}
|
||||
const gd::RectF lastRect = RowRect(last);
|
||||
if (lastRect.Y + lastRect.Height <= m_listRect.Y)
|
||||
{
|
||||
// 整组(含最后一行)已全部滚过去:组名跟随一起向上,不再出现。
|
||||
continue;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void SignalPanel::PaintStickyGroupRow(gd::Graphics& g, int row)
|
||||
{
|
||||
if (row < 0 || row >= static_cast<int>(m_rows.size()) ||
|
||||
m_rows[static_cast<std::size_t>(row)].kind != RowKind::GROUP)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float height = m_rows[static_cast<std::size_t>(row)].height;
|
||||
m_rowYOverrideActive = true;
|
||||
m_rowYOverrideY = m_listRect.Y;
|
||||
// 悬浮行必须是不透明的,否则下面的信号行会与组名叠在一起看不清;
|
||||
// `FillRowRect` 会把矩形夹到列表矩形(GDI 填充不受 GDI+ 裁剪约束)。
|
||||
FillRowRect(g, gd::RectF(m_listRect.X, m_listRect.Y, m_listRect.Width, height), m_theme->panelBg);
|
||||
PaintFileRow(g, row);
|
||||
// 底部一条边界线,让悬浮行与它下面滚动的信号行区分开。
|
||||
UiDrawLine(g, m_listRect.X, m_listRect.Y + height - 0.5f, m_listRect.X + m_listRect.Width,
|
||||
m_listRect.Y + height - 0.5f, m_theme->border, 1.0f);
|
||||
m_rowYOverrideActive = false;
|
||||
}
|
||||
|
||||
gd::RectF SignalPanel::FileCloseRect(int row) const
|
||||
{
|
||||
const gd::RectF rect = RowRect(row);
|
||||
@@ -980,8 +1055,15 @@ namespace jdi
|
||||
else
|
||||
{
|
||||
const int rowCount = static_cast<int>(m_rows.size());
|
||||
// 先定出要「悬浮」的分组行(若有):常规循环跳过它,最后统一钉在列表顶部绘制,
|
||||
// 否则它会先被画一次在滚动位置上、又被画一次在顶部(视觉上重叠两份)。
|
||||
const int stickyRow = StickyGroupRowIndex(rowCount);
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
if (row == stickyRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const gd::RectF rect = RowRect(row);
|
||||
if (rect.Y + rect.Height < m_listRect.Y || rect.Y > m_listRect.Y + m_listRect.Height)
|
||||
{
|
||||
@@ -996,6 +1078,8 @@ namespace jdi
|
||||
PaintSignalRow(g, row);
|
||||
}
|
||||
}
|
||||
|
||||
PaintStickyGroupRow(g, stickyRow);
|
||||
}
|
||||
|
||||
g.Restore(saveState);
|
||||
|
||||
Reference in new issue
Block a user