修复自适应/首帧卡顿回归 + 派生信号烘值 + 图例交互与托底重做

一、性能(详见 agent_mem §9.19)
- AutoRangeLaneToView:把 DataSet::Style(ref) 从逐采样循环里提出来(远端新增的「调整」
  功能让它每个采样点解析两次,实测 147ms 劣化到 1200ms),并在「视界覆盖整条信号且
  无任何数值变换」时直接取信号已缓存的 min/max(O(1))。工具栏「Y 轴自适应」在全视图下
  1200ms → 0.02ms,结果与逐点扫描逐位一致;有变换时仍逐点套用与绘制相同的变换。
- 新增 WarmLaneLayersStep():加载后宽基底图层从未分配,用户第一次平移/选区放大时
  Paint 会在那一帧同步重建全部网格图层(首帧 64.3ms)。现在改为在「命中静态场景缓存」
  (视界未变、非拖拽)时用既有预算机制每帧至多补 1 个网格,把整批重建摊到静止帧;
  选区放大首帧 64.3ms → 13.0ms。

二、派生信号
- 「构建为新信号」把源信号的「调整」变换(系数 → 绝对值 → 垂直镜像 → Y 偏移,以及
  水平镜像=数据反序)烘进派生采样值,新信号样式一律保持默认(不再是"继承的指纹");
  仅时间轴偏移 offsetTime 因派生信号与源信号共用同一个文件时间轴仍需继承。

三、交互
- 网格 GX 拖拽让位判据改为「鼠标指针」:指针越过相邻网格的中心水平线即让位并出现吸附
  提示(原先按"被拖网格的中心",要求把整个网格拖过去一格才吸附)。
- GX 徽标悬停高亮:徽标本体在装饰缓存里被冻结,高亮改为贴图之后的叠加层,并新增
  m_hoverBadge 状态驱动重绘/取消;QueryCursor 在徽标上返回 IDC_HAND —— 与左侧信号列表
  里同一个 GX 徽标一致。
- 修复「中键平移过视界之后图例悬停/点击/双击改名全部失效」:整幅重绘路径会清空
  m_legendHits,而装饰缓存命中时 PaintLaneLegend 提前 return 导致永不重建。现在把该函数
  拆成「几何 → 命中/装饰矩形 → 绘制」三段,任何路径都先重建命中矩形。

四、绘制
- 每个信号图例单独加圆角托底(配色/圆角风格与 GX 徽标一致);装饰矩形改为逐块
  (徽标一块 + 每个条目一块 + 每个 Y 轴胶囊一块),不再覆盖整条图例带;每块托底先用
  不透明底色铺满「外接整数像素再外扩 1px」的矩形再画圆角 —— 消除「GX/图例周边旧波形
  像素残留」(逐像素验证:装饰矩形内 0 差异)。
- 图例悬停高亮改为不透明托底并在其上重绘色块与文字(否则不透明托底会把图例内容盖住),
  同时保留对应信号的曲线光晕;Y 轴刻度胶囊补不透明外接底色,消除同类残留。

验证:离屏 Application harness(窗口 SW_HIDE + 直接注入鼠标消息 + 从后备缓冲区截图,
不占用鼠标/键盘/屏幕)40/40 通过;zoomcheck 新增 mode 20-26 量化;make release 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-14 01:55:34 +08:00
1 parent 940b8e67e6
commit 7c32eca004
4 files changed
+590 -183

No files matched your search

+45 -21
View File
@@ -1528,23 +1528,26 @@ namespace jdi
// 0. 先把源信号的「调整」变换快照下来(下面 `InsertDerivedSignal` 会移动信号存储,
// 之后再取 `source` 会悬垂)。
//
// ⚠️ **新信号必须继承这套变换**,否则会出现“预览是调整后的波形、构建出来的却按原始状态绘制”
//(用户报告的不一致):网格里的预览曲线绘制的是 `变换(滤波结果)`,而构建出的信号只存了
// `滤波结果`;把变换一并继承后,两者绘制完全相同。
// 另外**时间轴偏移只能靠继承**:派生信号与源信号共用同一个文件时间轴,无法把时间平移烘进数据。
SignalStyle inheritedTransform;
// ⚠️ **变换要"烘进数据",而不是让新信号继承这套指纹**(用户明确要求):
// 网格里的预览曲线画的是 `变换(滤波结果)`。若新信号只存 `滤波结果` 再**继承变换**,
// 它的「调整」面板里就会莫名其妙带着一整套系数 / 镜像 / 偏移 —— 用户看到的是
// "新信号只是继承了原信号的指纹,数据本身没变"。现在改为:
// * 数值域变换(系数 → 绝对值 → 垂直镜像 → Y 偏移)**与**水平镜像(数据反序)
// 全部作用在派生采样值上;
// * 于是新信号**用默认外观**绘制出来就与预览曲线逐位一致。
// * ⚠️ **时间轴偏移是唯一的例外**:它改变的是"绘制在哪个时刻",而派生信号与源信号
// 共用同一个文件时间轴,无法把时间平移烘进采样数据,因此仍由新信号的样式携带
// (否则新信号会相对预览整体横向错位)。
SignalStyle sourceTransform;
bool hasSourceStyle = false;
{
SignalRef sourceRef;
sourceRef.fileIndex = request.fileIndex;
sourceRef.signalIndex = request.signalIndex;
if (const SignalStyle* sourceStyle = m_dataSet.Style(sourceRef))
{
inheritedTransform.gain = sourceStyle->gain;
inheritedTransform.absolute = sourceStyle->absolute;
inheritedTransform.mirrorVertical = sourceStyle->mirrorVertical;
inheritedTransform.mirrorHorizontal = sourceStyle->mirrorHorizontal;
inheritedTransform.offsetY = sourceStyle->offsetY;
inheritedTransform.offsetTime = sourceStyle->offsetTime;
sourceTransform = *sourceStyle;
hasSourceStyle = true;
}
}
@@ -1567,11 +1570,33 @@ namespace jdi
std::to_wstring(request.iterations) + L"次迭代)";
}
// 2. 组装新信号(外观先沿用默认值,插入后再继承源信号的「调整」变换)。
// 2. 组装新信号(外观一律用默认值:变换已经烘进采样数据,不再是"继承的指纹")。
derived.rawHeader = derived.name;
derived.unit = source.unit;
derived.values.assign(derived_values.begin(), derived_values.end());
// 2b. 把源信号的「调整」变换**烘进派生采样值**:
// * 数值域(系数 → 绝对值 → 垂直镜像 → Y 偏移)逐点套用 `ApplyDrawTransform`;
// * 水平镜像 = 数据反序,等价于把采样数组整体反转。
// ⚠️ `ApplyDrawTransform` 是逐点函数,因此"先变换再反转"与"先反转再变换"等价。
if (hasSourceStyle)
{
const bool valueTransform =
(sourceTransform.gain != 1.0f || sourceTransform.absolute ||
sourceTransform.mirrorVertical || sourceTransform.offsetY != 0.0f);
if (valueTransform)
{
for (double& value : derived.values)
{
value = ApplyDrawTransform(sourceTransform, value);
}
}
if (sourceTransform.mirrorHorizontal)
{
std::reverse(derived.values.begin(), derived.values.end());
}
}
// 3. 插入到原信号的下方(数据层 + 各标签页引用重映射)。
int newIndex = -1;
// 插入派生信号会移动该文件的信号存储:先暂停后台计算,避免其读取已移动的数据。
@@ -1586,16 +1611,15 @@ namespace jdi
tab.RemapAfterSignalInsert(request.fileIndex, newIndex);
}
// 3b. 新信号继承源信号的「调整」变换(系数 / 绝对值 / 镜像 / 偏移),
// 使其绘制结果与网格里的预览曲线**完全一致**。
if (SignalStyle* derivedStyle = m_dataSet.Style(SignalRef{request.fileIndex, newIndex}))
// 3b. 仅把**时间轴偏移**带到新信号上(唯一无法烘进数据的一项,见上面第 0 步说明)。
// 其余「调整」项(系数 / 绝对值 / 镜像 / Y 偏移)保持在默认值 —— 它们已经
// 烘进了采样数据,因此新信号不经过任何"调整"就与预览曲线完全一致。
if (hasSourceStyle && sourceTransform.offsetTime != 0.0)
{
derivedStyle->gain = inheritedTransform.gain;
derivedStyle->absolute = inheritedTransform.absolute;
derivedStyle->mirrorVertical = inheritedTransform.mirrorVertical;
derivedStyle->mirrorHorizontal = inheritedTransform.mirrorHorizontal;
derivedStyle->offsetY = inheritedTransform.offsetY;
derivedStyle->offsetTime = inheritedTransform.offsetTime;
if (SignalStyle* derivedStyle = m_dataSet.Style(SignalRef{request.fileIndex, newIndex}))
{
derivedStyle->offsetTime = sourceTransform.offsetTime;
}
}
// 4. 在当前标签页中勾选新信号,并把它所在的网格移到原信号网格的下方。