「调整」行:系数/偏移 按钮 + 偏移调整(Y 轴/时间轴)二级面板;结算顺序加入偏移;派生信号继承调整变换

按钮与面板:
- 「系数」按钮改名为「系数/偏移」;点击展开的二级面板在原「系数调整」输入框下方新增「偏移调整」:
  与「系数调整」同一套绘制(标题 + 标题栏右侧的还原图标),下面两行分别是「Y轴」「时间轴」,
  每行左侧说明文字、右侧内联输入框(整行数值框)。
- 「Y轴」默认 0.00,非编辑态**始终带正负号**(+0.00 / -12.50),小数不足两位时补两位、更高精度保留更多位。
- 「时间轴」默认 0.00,非编辑态显示与双光标差值同一套复合单位文本(`Δt:500ms`、`Δt:1h 1min 2s`);
  进入编辑态时显示以**秒**为单位的数值,并在输入框最右侧提示单位 `s`。
- 「偏移调整」的还原图标把两个偏移恢复为默认值 0;「系数调整」的还原图标仍只重置系数。
- 面板/输入框的编辑初值与显示文本分离(`MenuItem::editValue` / `editSuffix`),
  非编辑态(`Δt:…`)与编辑态(`3662`)不一致时也能正确回显。

语义与结算顺序:
- 结算顺序变为 **系数 → 绝对值 → 镜像 → 偏移**(前三步不变)。
- Y 偏移:给绘制值加常数(波形上下移动);时间偏移:采样绘制在 `t + offsetTime` 处
  (例如 t=1s、偏移 -0.5s → 画在 0.5s)。
- ⚠️ Y 偏移以**未镜像空间**为准:垂直镜像开关变化时同步取反偏移量,
  使绘制等价于「先加上偏移再镜像」(开关来回切换可逆)。
- 绘制链路全部跟随偏移:`BuildPolyline`(逐点描边 / 像素列包络 / 跨列边界插值 / 粗粒度分组层,
  保序的 Y 偏移直接加在分组极值上)、派生预览曲线、光标取值读数(含 Δt)、
  可见区间 Y 轴自适应、整段自适应的区间映射;折线缓存键、场景指纹、样式快照与图层指纹都纳入两个偏移。

派生信号(实验性功能「构建为新信号」):
- 修复「预览是调整后的波形、构建出来却按原始状态绘制」:新信号**继承源信号的调整变换**
  (系数/绝对值/两个镜像/两个偏移)——网格预览绘制的是 `变换(滤波结果)`,
  而构建出的信号只存了滤波结果,因此必须把变换一并继承;
  时间轴偏移**只能**靠继承(派生信号与源信号共用同一文件时间轴,无法把时间平移烘进数据)。

离屏验证(不干扰用户):
- ⚠️ harness 现在在每次注入鼠标消息后**立即释放本窗口的鼠标捕获**
  (宿主 WM_LBUTTONDOWN 会 SetCapture,隐藏窗口同样会把用户的真实鼠标输入吸走);
  后续消息本来就是直接投递,无需捕获。
- 新增 [AP]:按钮标签不裁切、面板 5 行顺序与「偏移调整」位于系数输入框下方、
  Y轴/时间轴行的标签与输入框同行且默认值/单位提示正确、时间轴编辑态显示秒值并在最右侧提示 `s`、
  Y 偏移提交/显示正负号/波形整体平移量、时间偏移提交/Δt 复合单位(含 3662 → Δt:1h 1min 2s)/
  编辑态为秒、**按"偏移若干个采样点"构造的精确几何验证**(采样被画到移位后的像素位置、且不再画在原处)、
  读数跟随、两个还原图标、垂直镜像对 Y 偏移取反(可逆)、
  以及派生信号继承变换;[AL] 补充「系数→绝对值→镜像→偏移」的顺序与区间映射(含 Y 偏移)。
- 合计 645/645 通过;make clean && make all 零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-13 17:56:00 +08:00
1 parent 2b02518f85
commit fc77b0262f
11 files changed
+567 -252

No files matched your search

+133 -2
View File
@@ -24,6 +24,13 @@ namespace jdi
{
return c >= L'A' && c <= L'Z';
}
/** @brief 时间间隔的显示单位。 */
struct DurationUnit
{
double scale = 1.0; /**< 1 个该单位等于多少秒。 */
const wchar_t* name = L"s"; /**< 单位名称。 */
};
}
std::wstring Utf8ToWide(const char* text, int length)
@@ -339,9 +346,133 @@ namespace jdi
return std::wstring(buffer);
}
std::wstring FormatByteSize(unsigned long long bytes)
std::wstring FormatDurationCompound(double seconds)
{
// 单位自适应:GB / MB / KB(阈值按 1024 进制)。
if (!std::isfinite(seconds))
{
return L"—";
}
const double value = std::fabs(seconds);
if (value <= 0.0)
{
return L"0s";
}
// ---- 顶级单位高于 s(h / min):整合为 h/min/s(整数秒)----
// 注意条件必须是 >= 60s:1~60s 属于"顶级单位为 s"的情况,需按下一条规则处理
// (非整数秒要递进到 ms,例如 5.06s → "5s 60ms")。
if (value >= 60.0)
{
const long long totalSeconds = static_cast<long long>(std::llround(value));
long long hours = 0;
long long minutes = 0;
long long secs = totalSeconds;
if (totalSeconds >= 3600)
{
hours = totalSeconds / 3600;
secs = totalSeconds % 3600;
}
if (secs >= 60)
{
minutes = secs / 60;
secs = secs % 60;
}
std::wstring text;
const auto append = [&text](long long amount, const wchar_t* unit)
{
if (amount <= 0)
{
return;
}
if (!text.empty())
{
text += L" ";
}
text += std::to_wstring(amount);
text += unit;
};
append(hours, L"h");
append(minutes, L"min");
append(secs, L"s");
if (!text.empty())
{
return text;
}
}
// ---- 顶级单位为 s:整数秒 + 可选 ms ----
if (value >= 1.0)
{
const double whole = std::floor(value);
const double fraction = value - whole;
std::wstring text = FormatValue(whole) + L"s";
const double remainderMs = fraction * 1000.0;
if (remainderMs >= 1.0e-6)
{
text += L" ";
text += FormatValue(remainderMs);
text += L"ms";
}
return text;
}
// ---- 顶级单位低于 s:ms / us / ns / ps ----
const DurationUnit subUnits[] = {
{1.0e-3, L"ms"}, {1.0e-6, L"us"}, {1.0e-9, L"ns"}, {1.0e-12, L"ps"},
};
const std::size_t subCount = sizeof(subUnits) / sizeof(subUnits[0]);
std::size_t index = subCount - 1;
for (std::size_t i = 0; i < subCount; ++i)
{
if (value >= subUnits[i].scale)
{
index = i;
break;
}
}
// 顶级单位为 ps:不再递进次级单位(保留小数)。
if (index + 1 >= subCount)
{
return FormatValue(value / subUnits[index].scale) + L" " + subUnits[index].name;
}
const double scaled = value / subUnits[index].scale;
double whole = std::floor(scaled);
double fraction = scaled - whole;
// 浮点误差可能导致 fraction 极接近 1(例如 4.9999999),归一化一下。
if (fraction >= 1.0 - 1.0e-9)
{
whole += 1.0;
fraction = 0.0;
}
std::wstring text = FormatValue(whole);
text += subUnits[index].name;
// 仍是该单位的整数 → 不显示下级单位。
const double remainder = fraction * subUnits[index].scale;
const double inNext = remainder / subUnits[index + 1].scale;
if (inNext >= 1.0e-6)
{
text += L" ";
text += FormatValue(inNext);
text += subUnits[index + 1].name;
}
return text;
}
std::wstring FormatDurationSeconds(double seconds)
{
if (!std::isfinite(seconds))
{
return L"0";
}
return FormatValue(std::fabs(seconds));
}
std::wstring FormatByteSize(unsigned long long bytes)
{ // 单位自适应:GB / MB / KB(阈值按 1024 进制)。
constexpr double kKilo = 1024.0;
constexpr double kMega = kKilo * 1024.0;
constexpr double kGiga = kMega * 1024.0;