均值面板越界根因 + 网格线进入主题配色 + 条目栏悬停/整行点击 + 均值点数下限 1
1. 「元素超出浮动面板」真根因(用户 2026-10-30 报告的截图)
- 绘制侧 PaintEdgeCursor 的 itemized 仍是 (centerAxis || meanFilter || weakPeak),
而布局侧 EdgePanelLayoutFor 早已是 (centerAxis || weakPeak)。
于是均值滤波面板被旧的「逐条目」分支**再画一遍**(每行名称 + 复选框,且没有任何裁切),
滚动后那些行的坐标是负值/越界 ⇒ 直接画到标题栏上。
- 修法:绘制侧与布局侧统一;旧分支的兜底循环跳过条件补 meanFilter。
底部「构建为新信号」由共享的 if (hasButton) 绘制,不受影响。
- 顺带删除「只画完全落在内容区里的行」的 fullyInside 门控:真因修掉后
g.SetClip(layout.content) 已足够,半截行正常显示,滚动观感连续。
2. 条目栏交互(与「自由标记」分组栏一致)
- 整行可点:复选框区 = 切换是否预览;已启用 = 展开/收起参数行;未启用 = 直接开始预览。
命中只要求点落在 layout.content 内(不再要求整行在内容区内),因此半截行也能操作。
- 悬停行 → 对应预览曲线高亮:新增 m_meanRowHoverLane / m_meanRowHoverRef,
由 OnMouseMove 维护、OnMouseLeave 与 InvalidateDerivedCaches 清除;
PaintDerivedCurve/PaintMeanFilterCurve/PaintWeakPeakCurve 新增 bool highlight,
先画 +5px 半透明同色光晕再把线加粗,行头叠一层 accent;悬停态变化会 InvalidateSceneCache。
3. 均值点数下限 1(用户要求:不可以为 0)
- kMeanFilterPointsMin 0 → 1;输入提交用 centerTarget ? 0 : kMeanFilterPointsMin
(中心轴边沿计数沿用旧语义 0 = 不平滑);预览与构建请求都再夹一次下限,
即使条目被外部写成 0,渲染/构建结果与 1 点逐位一致。
4. 网格线颜色成为主题配色的一部分
- struct Theme 新增 laneBg(网格叠底色);NormalizePlotContrast → FinalizeTheme,
先解 laneBg = EnsureContrast(plotBg, cardBg, 1.03),再让 gridWeak/gridStrong/
axisLine/borderStrong 统一对 laneBg 归一(1.30 / 1.70 / 2.10 / 1.70)。
- 四个主题各自给出显式取值:Dark 的网格线对叠底色原本只有 1.071(几乎不可见)⇒ 现 ≥1.30;
其它三个主题同步微调,网格可见性一致。ThemeLaneBackground 返回 laneBg。
5. 行三角尺寸统一
- 新增 kEdgePanelRowTriangleSize = 12 逻辑像素,均值滤波行与自由标记分组栏共用;
标题栏的 kEdgePanelDisclosureWidth = 20 保持不变。
6. 均值面板滚动条与文本
- 滚动条必须在 g.Restore(meanSave) 之后绘制(它在内容裁剪区之外,裁剪内画会被整条裁掉)。
- 滑块标签/数值改用 UiTextClipped(..., layout.content)(GDI 文本不受 GDI+ 裁剪约束)。
验证(全部离屏)
uicheck 69/69、lbcheck 21/21、lbresid 5/5、lbzorder 4/4、lbcovr 7/7、lbpanel 10/10、
derivecheck 16/16、round15check 28/28、themecheck 17/17、.preflight/meancheck 17/17;
.preflight/meanpix:滚到顶/中/底时标题栏与面板外均为 0 像素差异,内容区正常滚动;
rendercheck:A–H 八个场景哈希与基线完全一致;
make release 零 warning(805,376 B);make debug 零 warning。
同步文档:Documents/agent_mem.md 追加「2026-10-30 第十七轮」,
并把第十六轮里「均值点数(0…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
This commit is contained in:
1 parent
bdd703b88b
commit
2d83b494a3
9 files changed
+396
-100
No files matched your search
@@ -699,8 +699,11 @@ namespace jdi
|
||||
}
|
||||
const int maximum = isPoints ? 1000000 : 1000;
|
||||
int value = static_cast<int>(std::lround(parsed));
|
||||
// 迭代次数至少为 1(1 次迭代等于不做滤波);均值点数可以为 0。
|
||||
value = isPoints ? std::max(0, std::min(value, maximum))
|
||||
// 迭代次数至少为 1(1 次迭代等于不做滤波)。
|
||||
// 「均值滤波」的**均值点数下限为 1**(用户 2026-10-30 明确要求:不能为 0);
|
||||
// 「中心轴边沿计数」沿用旧语义(0 = 不做平滑),因此两者下限分开取。
|
||||
const int pointsMin = centerTarget ? 0 : kMeanFilterPointsMin;
|
||||
value = isPoints ? std::max(pointsMin, std::min(value, maximum))
|
||||
: std::max(1, std::min(value, maximum));
|
||||
|
||||
Lane::MeanFilterItem& item = centerTarget ? lane.EnsureEdgeCenterItem(m_editMeanRef)
|
||||
@@ -1824,6 +1827,49 @@ namespace jdi
|
||||
hoverPanelKind);
|
||||
}
|
||||
|
||||
// 「均值滤波 / 弱峰调整」面板**条目栏**的悬停 → 高亮该信号的预览曲线
|
||||
//(用户 2026-10-30 要求:"鼠标处于条目栏上方时,对应的预览曲线需要高亮提示")。
|
||||
{
|
||||
int hoverLane = -1;
|
||||
SignalRef hoverRef;
|
||||
if (overEdgePanel && m_tab != nullptr && panelLane >= 0 &&
|
||||
(static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::MEAN ||
|
||||
static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::PEAK))
|
||||
{
|
||||
const EdgePanelLayout layout =
|
||||
EdgePanelLayoutFor(panelLane, static_cast<EdgePanelKind>(hoverPanelKind));
|
||||
if (UiPointInRect(layout.content, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
if (rowIndex >= layout.rowHeaders.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
const gd::RectF& headerRect = layout.rowHeaders[rowIndex];
|
||||
// 半截滚出的行**也算命中**:绘制时行本来就会被裁剪成半截显示,
|
||||
// 若这里要求“完整落在内容区内”,就会出现“看得见却悬停不到”的不一致。
|
||||
// 命中点本身已被限定在内容区里,因此不会出现“看得见一半却点到”的幽灵热区。
|
||||
if (UiPointInRect(headerRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
hoverLane = panelLane;
|
||||
hoverRef = layout.rowRefs[rowIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoverLane != m_meanRowHoverLane || !(hoverRef == m_meanRowHoverRef))
|
||||
{
|
||||
m_meanRowHoverLane = hoverLane;
|
||||
m_meanRowHoverRef = hoverRef;
|
||||
// 预览曲线在部分绘制路径里属于"静态场景"(整幅重绘会缓存它),
|
||||
// 因此悬停状态一变就作废场景缓存 —— 只在"进出某一行"时发生,不是每帧。
|
||||
InvalidateSceneCache();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 「弱峰调整」每行滑块右侧的**复位控件**:悬停时给出一次性提示(进入时设置、离开时清除)。
|
||||
// 与「系数/偏移」二级面板标题栏的还原图标同一交互语义(悬停高亮 + 手型 + 提示)。
|
||||
bool overPeakReset = false;
|
||||
@@ -2216,18 +2262,8 @@ namespace jdi
|
||||
{
|
||||
break;
|
||||
}
|
||||
// 只允许命中**完全落在内容区里**的行(部分滚出的行其矩形会与内容区
|
||||
// 边界重叠,放行会造出"看得见一半却能点"的幽灵热区)。
|
||||
if (rowIndex < layout.rowHeaders.size())
|
||||
{
|
||||
const gd::RectF& headerRect = layout.rowHeaders[rowIndex];
|
||||
if (headerRect.Y < layout.content.Y - 0.5f ||
|
||||
headerRect.Y + headerRect.Height >
|
||||
layout.content.Y + layout.content.Height + 0.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 行可以“半截”显示(绘制时会按内容区裁剪),因此这里不再要求整行落在内容区;
|
||||
// 命中点已由上面的 `insideContent` 限定在内容区内,不会抢走底部按钮的点击。
|
||||
const bool hasParams =
|
||||
rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f &&
|
||||
@@ -2334,13 +2370,38 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 组头左侧三角:切换参数行的收起 / 展开(只有勾选后才有三角)。
|
||||
if (rowIndex < layout.rowTriangles.size() &&
|
||||
layout.rowTriangles[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowTriangles[rowIndex], fx, fy))
|
||||
// 条目栏(信号色叠底那一整行):**整行都能点**(用户 2026-10-30 要求,
|
||||
// 与「自由标记」分组栏一致:不再只能点小小的展开三角)。
|
||||
// 语义:
|
||||
// * 点在右端复选框上 → 切换"是否启用该信号的预览"(与复选框同一语义,
|
||||
// 因为整行已被本函数消费,复选框自己收不到点击了);
|
||||
// * 已在预览中 → 切换参数行的收起 / 展开;
|
||||
// * 尚未启用 → 直接启用(用户点这一行显然是想看它的预览)。
|
||||
if (rowIndex < layout.rowHeaders.size() &&
|
||||
layout.rowHeaders[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowHeaders[rowIndex], fx, fy))
|
||||
{
|
||||
Lane::MeanFilterItem& item = meanLane.EnsureMeanFilterItem(ref);
|
||||
item.collapsed = !item.collapsed;
|
||||
const bool onCheckBox =
|
||||
rowIndex < layout.rowChecks.size() &&
|
||||
layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowChecks[rowIndex], fx, fy);
|
||||
const Lane::MeanFilterItem* existing = meanLane.FindMeanFilterItem(ref);
|
||||
if (onCheckBox)
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
}
|
||||
else if (existing == nullptr)
|
||||
{
|
||||
// 未启用:点击整行即开始预览(用户点这一行显然是想看它的预览)。
|
||||
meanLane.EnsureMeanFilterItem(ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已启用:整行切换参数行的收起 / 展开。
|
||||
meanLane.EnsureMeanFilterItem(ref).collapsed = !existing->collapsed;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
@@ -2564,8 +2625,9 @@ namespace jdi
|
||||
MeanFilterBuild build;
|
||||
build.groupIndex = ref.groupIndex;
|
||||
build.signalIndex = ref.signalIndex;
|
||||
build.points = item->points;
|
||||
build.iterations = item->iterations;
|
||||
// 与预览一致:点数下限 1、迭代次数下限 1(用户 2026-10-30 要求)。
|
||||
build.points = std::max(kMeanFilterPointsMin, item->points);
|
||||
build.iterations = std::max(kMeanFilterIterationsMin, item->iterations);
|
||||
m_pendingMeanFilterItems.push_back(build);
|
||||
}
|
||||
if (!m_pendingMeanFilterItems.empty())
|
||||
@@ -3398,6 +3460,13 @@ namespace jdi
|
||||
m_freeMarkerRowHoverItem = static_cast<std::size_t>(-1);
|
||||
changed = true;
|
||||
}
|
||||
// 「均值滤波 / 弱峰调整」条目栏的悬停高亮同样随鼠标离开清除(否则预览曲线一直亮着)。
|
||||
if (m_meanRowHoverLane != -1 || m_meanRowHoverRef.IsValid())
|
||||
{
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
changed = true;
|
||||
}
|
||||
if (m_cursorHover)
|
||||
{
|
||||
m_cursorHover = false;
|
||||
|
||||
Reference in new issue
Block a user