「调整」行:系数/偏移 按钮 + 偏移调整(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:
1 parent
2b02518f85
commit
fc77b0262f
11 files changed
+567
-252
No files matched your search
@@ -167,18 +167,18 @@ namespace jdi
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把系数格式化为界面文本:**至少两位小数**,精度更高时按实际精度显示。
|
||||
* @brief 数值文本:**至少两位小数**,精度更高时按实际精度显示。
|
||||
*
|
||||
* 例:`1` → `1.00`、`2.5` → `2.50`、`0.125` → `0.125`、`1.2345678` → `1.2345678`。
|
||||
* 实现:从 2 位小数开始递增位数,直到文本能精确表示该系数(相对误差 1e-7,与 float 精度匹配)。
|
||||
* 实现:从 2 位小数开始递增位数,直到文本能精确表示该数值
|
||||
*(容差 1e-7 相对量,与 float 精度匹配)。
|
||||
*
|
||||
* @param [IN] gain 系数。
|
||||
* @param [IN] value 数值。
|
||||
* @return 显示文本。
|
||||
*/
|
||||
std::wstring FormatGainText(float gain)
|
||||
std::wstring FormatNumberText(double value)
|
||||
{
|
||||
const double value = static_cast<double>(gain);
|
||||
const double tolerance = std::max(1.0e-9, std::fabs(value) * 1.0e-7);
|
||||
const double tolerance = std::max(1.0e-12, std::fabs(value) * 1.0e-7);
|
||||
for (int decimals = 2; decimals <= 9; ++decimals)
|
||||
{
|
||||
const std::wstring text = jdi::FormatNumber(value, decimals);
|
||||
@@ -190,6 +190,27 @@ namespace jdi
|
||||
}
|
||||
return jdi::FormatNumber(value, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带正负符号的数值文本:正数也带 `+`(非编辑态显示偏移量时使用)。
|
||||
* @param [IN] value 数值。
|
||||
* @return 形如 `+1.50` / `-0.25` / `+0.00` 的文本。
|
||||
*/
|
||||
std::wstring FormatSignedNumber(double value)
|
||||
{
|
||||
const std::wstring text = FormatNumberText(std::fabs(value));
|
||||
return ((value < 0.0) ? L"-" : L"+") + text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把系数格式化为界面文本(与偏移量同一套两位小数规则)。
|
||||
* @param [IN] gain 系数。
|
||||
* @return 显示文本。
|
||||
*/
|
||||
std::wstring FormatGainText(float gain)
|
||||
{
|
||||
return FormatNumberText(static_cast<double>(gain));
|
||||
}
|
||||
}
|
||||
|
||||
SignalPanel::SignalPanel()
|
||||
@@ -1291,8 +1312,9 @@ namespace jdi
|
||||
UiText(g, label, m_fonts->uiSmall, rect,
|
||||
checked ? m_theme->textStrong : m_theme->text, TextAlign::CENTER);
|
||||
};
|
||||
// 系数:仅当系数**不等于默认值 1** 时才显示为"已启用"。
|
||||
paintToggle(kAdjustGain, L"系数", !GainIsDefault(style->gain), StyleHit::GAIN_ADJUST);
|
||||
// 系数/偏移:系数不等于默认值 1、或任一偏移不为 0 时显示为"已启用"。
|
||||
paintToggle(kAdjustGain, L"系数/偏移",
|
||||
!GainIsDefault(style->gain) || style->Offset(), StyleHit::GAIN_ADJUST);
|
||||
// 镜像:水平或垂直任一被勾选即显示为激活态。
|
||||
paintToggle(kAdjustMirror, L"镜像", style->Mirrored(), StyleHit::MIRROR_ADJUST);
|
||||
paintToggle(kAdjustAbsolute, L"绝对值", style->absolute, StyleHit::ABSOLUTE_TOGGLE);
|
||||
@@ -1977,23 +1999,56 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// 二级面板:标题行(右侧带「还原」图标按钮)+ 一整行系数输入框
|
||||
//(与网格 Y 轴上下限的菜单内联编辑同一套做法:提交后面板保持打开,可连续微调)。
|
||||
// 二级面板:两张“标题(带还原图标)+ 输入框”的组合:
|
||||
// 1. 「系数调整」:一整行系数输入框;
|
||||
// 2. 「偏移调整」:下方两行分别为「Y轴」与「时间轴」的偏移输入框。
|
||||
//(与网格 Y 轴上下限的菜单内联编辑同一套做法:提交后面板保持打开,可连续微调。)
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem header;
|
||||
header.kind = MenuItemKind::HEADER_ACTION;
|
||||
header.label = L"系数调整";
|
||||
header.id = CMD_GAIN_RESET; // 点击标题栏右侧的还原图标 → 系数重置为 1
|
||||
items.push_back(header);
|
||||
MenuItem gainHeader;
|
||||
gainHeader.kind = MenuItemKind::HEADER_ACTION;
|
||||
gainHeader.label = L"系数调整";
|
||||
gainHeader.id = CMD_GAIN_RESET; // 点击标题栏右侧的还原图标 → 系数重置为 1
|
||||
items.push_back(gainHeader);
|
||||
|
||||
MenuItem edit;
|
||||
edit.kind = MenuItemKind::EDIT;
|
||||
edit.label.clear(); // 不再显示「绘制系数」说明文字:输入框独占整行
|
||||
edit.id = CMD_GAIN_EDIT;
|
||||
edit.value = FormatGainText(style->gain);
|
||||
edit.filter = EditFilter::NUMBER;
|
||||
items.push_back(edit);
|
||||
const std::wstring gainText = FormatGainText(style->gain);
|
||||
MenuItem gainEdit;
|
||||
gainEdit.kind = MenuItemKind::EDIT;
|
||||
gainEdit.label.clear(); // 不显示说明文字:输入框独占整行
|
||||
gainEdit.id = CMD_GAIN_EDIT;
|
||||
gainEdit.value = gainText;
|
||||
gainEdit.editValue = gainText;
|
||||
gainEdit.filter = EditFilter::NUMBER;
|
||||
items.push_back(gainEdit);
|
||||
|
||||
MenuItem offsetHeader;
|
||||
offsetHeader.kind = MenuItemKind::HEADER_ACTION;
|
||||
offsetHeader.label = L"偏移调整";
|
||||
offsetHeader.id = CMD_OFFSET_RESET; // 点击还原图标 → 两个偏移都恢复默认值
|
||||
items.push_back(offsetHeader);
|
||||
|
||||
// 「Y轴」:对绘制值加上一个常数偏移(波形上下移动),非编辑态带正负号。
|
||||
MenuItem offsetY;
|
||||
offsetY.kind = MenuItemKind::EDIT;
|
||||
offsetY.label = L"Y轴";
|
||||
offsetY.id = CMD_OFFSET_Y;
|
||||
offsetY.value = FormatSignedNumber(static_cast<double>(style->offsetY));
|
||||
offsetY.editValue = FormatNumberText(static_cast<double>(style->offsetY));
|
||||
offsetY.filter = EditFilter::NUMBER;
|
||||
items.push_back(offsetY);
|
||||
|
||||
// 「时间轴」:把采样沿时间轴平移(画在 t + offsetTime)。
|
||||
// 非编辑态显示与双光标差值同一套复合单位文本;编辑态显示以秒为单位的数值,
|
||||
// 并在输入框最右侧提示单位 `s`。
|
||||
MenuItem offsetTime;
|
||||
offsetTime.kind = MenuItemKind::EDIT;
|
||||
offsetTime.label = L"时间轴";
|
||||
offsetTime.id = CMD_OFFSET_TIME;
|
||||
offsetTime.value = L"Δt:" + FormatDurationCompound(style->offsetTime);
|
||||
offsetTime.editValue = FormatNumberText(style->offsetTime);
|
||||
offsetTime.editSuffix = L"s";
|
||||
offsetTime.filter = EditFilter::NUMBER;
|
||||
items.push_back(offsetTime);
|
||||
|
||||
// 锚定在按钮下沿:面板紧贴按钮展开(与拾色器"以触发器矩形为锚点"的观感一致)。
|
||||
const gd::RectF& button = m_adjustRects[kAdjustGain];
|
||||
@@ -2820,30 +2875,75 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// 「系数」二级面板里的输入框提交:就地更新绘制系数,面板保持打开
|
||||
// 「系数」/「偏移」二级面板里的输入框提交:就地更新,面板保持打开
|
||||
//(与网格 Y 轴上下限的菜单内联编辑一致),并把夹取后的真实值回写到输入框上。
|
||||
if (result.editCommit && result.id == CMD_GAIN_EDIT)
|
||||
if (result.editCommit)
|
||||
{
|
||||
SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style != nullptr)
|
||||
{
|
||||
wchar_t* stop = nullptr;
|
||||
const std::wstring trimmed = Trim(result.editText);
|
||||
const double parsed = std::wcstod(trimmed.c_str(), &stop);
|
||||
wchar_t* stop = nullptr;
|
||||
const double parsed =
|
||||
trimmed.empty() ? 0.0 : std::wcstod(trimmed.c_str(), &stop);
|
||||
// 非法/空输入保持原值(只把真实值回写到输入框,避免用户看到错误的数字)。
|
||||
if (!trimmed.empty() && stop != trimmed.c_str() && std::isfinite(parsed))
|
||||
const bool valid = !trimmed.empty() && stop != trimmed.c_str() && std::isfinite(parsed);
|
||||
switch (result.id)
|
||||
{
|
||||
style->gain = static_cast<float>(std::max(-kGainLimit, std::min(parsed, kGainLimit)));
|
||||
case CMD_GAIN_EDIT:
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
style->gain =
|
||||
static_cast<float>(std::max(-kGainLimit, std::min(parsed, kGainLimit)));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain), FormatGainText(style->gain));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
case CMD_OFFSET_Y:
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain));
|
||||
if (valid)
|
||||
{
|
||||
style->offsetY = static_cast<float>(
|
||||
std::max(-static_cast<double>(kGainLimit), std::min(parsed, kGainLimit)));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(
|
||||
CMD_OFFSET_Y, FormatSignedNumber(static_cast<double>(style->offsetY)),
|
||||
FormatNumberText(static_cast<double>(style->offsetY)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case CMD_OFFSET_TIME:
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
style->offsetTime =
|
||||
std::max(-kGainLimit, std::min(parsed, kGainLimit));
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_TIME,
|
||||
L"Δt:" + FormatDurationCompound(style->offsetTime),
|
||||
FormatNumberText(style->offsetTime));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 标题栏右侧的「还原」图标:把系数重置为默认值 1(面板保持打开,输入框立即回显)。
|
||||
// 标题栏右侧的「还原」图标:系数重置为默认值 1(面板保持打开,输入框立即回显)。
|
||||
if (result.id == CMD_GAIN_RESET)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||
@@ -2851,7 +2951,24 @@ namespace jdi
|
||||
style->gain = 1.0f;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain));
|
||||
m_menu->UpdateItemValue(CMD_GAIN_EDIT, FormatGainText(style->gain), FormatGainText(style->gain));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 「偏移调整」的「还原」图标:Y 与时间偏移都恢复为默认值 0。
|
||||
if (result.id == CMD_OFFSET_RESET)
|
||||
{
|
||||
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||
{
|
||||
style->offsetY = 0.0f;
|
||||
style->offsetTime = 0.0;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_Y, FormatSignedNumber(0.0), L"0.00");
|
||||
m_menu->UpdateItemValue(CMD_OFFSET_TIME, L"Δt:" + FormatDurationCompound(0.0),
|
||||
L"0.00");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -2865,6 +2982,18 @@ namespace jdi
|
||||
const bool horizontal = (result.id == CMD_MIRROR_HORIZONTAL);
|
||||
bool& flag = horizontal ? style->mirrorHorizontal : style->mirrorVertical;
|
||||
flag = !flag;
|
||||
if (!horizontal)
|
||||
{
|
||||
// ⚠️ Y 偏移以**未镜像空间**为准:垂直镜像开关变化时同步取反偏移量,
|
||||
// 使绘制等价于「先加上偏移再镜像」(用户明确要求)。
|
||||
style->offsetY = -style->offsetY;
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemValue(
|
||||
CMD_OFFSET_Y, FormatSignedNumber(static_cast<double>(style->offsetY)),
|
||||
FormatNumberText(static_cast<double>(style->offsetY)));
|
||||
}
|
||||
}
|
||||
if (m_menu != nullptr)
|
||||
{
|
||||
m_menu->UpdateItemLabel(result.id, horizontal ? L"水平镜像" : L"垂直镜像", flag, true);
|
||||
|
||||
Reference in new issue
Block a user