「调整」行:系数/偏移 按钮 + 偏移调整(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
@@ -215,10 +215,13 @@ namespace jdi
|
||||
|
||||
float PopupMenu::NaturalEditFieldWidth(const MenuItem& item) const
|
||||
{
|
||||
const std::wstring& widest =
|
||||
(item.editValue.size() > item.value.size()) ? item.editValue : item.value;
|
||||
const float natural = std::max(kEditFieldWidth * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, item.value) + 20.0f * m_scale);
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, widest) + 20.0f * m_scale);
|
||||
const float withSuffix = natural + EditSuffixWidth(item);
|
||||
// 无说明文字 = 整行输入框(如「系数调整」面板):给一个更宽的默认值,视觉上独占整行。
|
||||
return item.label.empty() ? std::max(natural, kRowFieldWidth * m_scale) : natural;
|
||||
return item.label.empty() ? std::max(withSuffix, kRowFieldWidth * m_scale) : withSuffix;
|
||||
}
|
||||
|
||||
void PopupMenu::CloseSubmenu()
|
||||
@@ -346,6 +349,15 @@ namespace jdi
|
||||
return -1;
|
||||
}
|
||||
|
||||
float PopupMenu::EditSuffixWidth(const MenuItem& item) const
|
||||
{
|
||||
if (item.kind != MenuItemKind::EDIT || item.editSuffix.empty())
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return UiMeasureTextWidth(m_fonts->uiSmall, item.editSuffix) + 8.0f * m_scale;
|
||||
}
|
||||
|
||||
gd::RectF PopupMenu::EditFieldRect(int index) const
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(m_items.size()))
|
||||
@@ -420,8 +432,8 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// 输入框初值取当前显示值,整段选中,便于直接键入新数值。
|
||||
m_edit.Begin(item.value, true, item.filter, 0u);
|
||||
// 输入框初值取「编辑初值」(未设置时用显示值),整段选中,便于直接键入新数值。
|
||||
m_edit.Begin(item.editValue.empty() ? item.value : item.editValue, true, item.filter, 0u);
|
||||
m_editIndex = index;
|
||||
m_hoverIndex = index;
|
||||
return true;
|
||||
@@ -445,8 +457,10 @@ namespace jdi
|
||||
}
|
||||
|
||||
MenuItem& item = m_items[static_cast<std::size_t>(index)];
|
||||
// 立即回写显示值,使菜单上的数值与数据层一致(宿主会在派发后再纠正为夹取后的真实值)。
|
||||
// 立即回写显示值,使菜单上的数值与数据层一致(宿主会在派发后再纠正为夹取后的真实值);
|
||||
// 键入文本同时记为**编辑初值**(`value` 与 `editValue` 不一致的行也能正确回显上次输入)。
|
||||
item.value = text;
|
||||
item.editValue = text;
|
||||
|
||||
result.valid = true;
|
||||
result.scope = m_scope;
|
||||
@@ -471,17 +485,23 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
bool PopupMenu::UpdateItemValue(int id, const std::wstring& value)
|
||||
bool PopupMenu::UpdateItemValue(int id, const std::wstring& value, const std::wstring& editValue)
|
||||
{
|
||||
for (MenuItem& item : m_items)
|
||||
{
|
||||
if (item.id == id && item.kind == MenuItemKind::EDIT)
|
||||
{
|
||||
if (item.value == value)
|
||||
const bool valueSame = (item.value == value);
|
||||
const bool editSame = editValue.empty() || (item.editValue == editValue);
|
||||
if (valueSame && editSame)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
item.value = value;
|
||||
if (!editValue.empty())
|
||||
{
|
||||
item.editValue = editValue;
|
||||
}
|
||||
RebuildLayout();
|
||||
return true;
|
||||
}
|
||||
@@ -1212,9 +1232,31 @@ namespace jdi
|
||||
UiText(g, UiEllipsize(item.label, m_fonts->uiSmall, labelWidth), m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, rect.Y, labelWidth, rect.Height), labelColor, TextAlign::LEFT);
|
||||
|
||||
const float suffix = EditSuffixWidth(item);
|
||||
if (static_cast<int>(index) == m_editIndex)
|
||||
{
|
||||
UiDrawEditField(g, fieldRect, m_edit, m_fonts->uiSmall, m_scale);
|
||||
// 编辑态:输入框底板覆盖整行,正文区向右让出后缀宽度,
|
||||
// 后缀(单位提示)靠右对齐画在它右侧。
|
||||
// ⚠️ 必须用 `UiDrawEditFieldContent`(只画内容)——`UiDrawEditField`
|
||||
// 会把底板/描边按传入矩形绘制,那样带后缀时外框会短一截。
|
||||
const gd::RectF textRect =
|
||||
(suffix > 0.0f)
|
||||
? gd::RectF(fieldRect.X, fieldRect.Y, std::max(12.0f, fieldRect.Width - suffix),
|
||||
fieldRect.Height)
|
||||
: fieldRect;
|
||||
UiFillRoundRect(g, fieldRect, 3.0f * m_scale, m_theme->cardBg);
|
||||
UiDrawRoundRect(g, fieldRect, 3.0f * m_scale, m_theme->accent, 1.6f);
|
||||
UiDrawEditFieldContent(g, textRect, m_edit, m_fonts->uiSmall, m_scale);
|
||||
if (suffix > 0.0f)
|
||||
{
|
||||
const float suffixLeft = textRect.X + textRect.Width;
|
||||
UiText(g, item.editSuffix, m_fonts->uiSmall,
|
||||
gd::RectF(suffixLeft, fieldRect.Y,
|
||||
std::max(4.0f, fieldRect.X + fieldRect.Width - suffixLeft -
|
||||
4.0f * m_scale),
|
||||
fieldRect.Height),
|
||||
m_theme->textMuted, TextAlign::RIGHT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user