均值面板越界根因 + 网格线进入主题配色 + 条目栏悬停/整行点击 + 均值点数下限 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
@@ -2063,6 +2063,9 @@ namespace jdi
|
||||
}
|
||||
const int saveState = g.Save();
|
||||
g.SetClip(lane.rect, gd::CombineModeIntersect);
|
||||
// 面板条目栏的悬停高亮(用户 2026-10-30 要求):鼠标停在哪一行,就把那条**预览曲线**
|
||||
// 画一圈光晕 + 加粗。`m_meanRowHoverLane/Ref` 由 `OnMouseMove` 维护。
|
||||
const bool hoverActive = (m_meanRowHoverLane == laneIndex) && m_meanRowHoverRef.IsValid();
|
||||
for (const SignalRef& ref : tabLane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
@@ -2070,6 +2073,7 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool hovered = hoverActive && (ref == m_meanRowHoverRef);
|
||||
if (centerOn)
|
||||
{
|
||||
const Lane::EdgeCenterItem* centerItem = tabLane.FindEdgeCenterItem(ref);
|
||||
@@ -2083,7 +2087,7 @@ namespace jdi
|
||||
const Lane::MeanFilterItem* item = tabLane.FindMeanFilterItem(ref);
|
||||
if (item != nullptr)
|
||||
{
|
||||
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations);
|
||||
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations, hovered);
|
||||
}
|
||||
}
|
||||
if (weakPeakOn)
|
||||
@@ -2097,7 +2101,7 @@ namespace jdi
|
||||
const int boostIterations = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit, item->iterations));
|
||||
PaintWeakPeakCurve(g, resolved, lane, amountPercent, boostIterations);
|
||||
PaintWeakPeakCurve(g, resolved, lane, amountPercent, boostIterations, hovered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3158,7 +3162,7 @@ namespace jdi
|
||||
|
||||
void PlotView::PaintDerivedCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, const std::vector<float>& curve,
|
||||
std::size_t curveBegin) const
|
||||
std::size_t curveBegin, bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -3273,6 +3277,27 @@ namespace jdi
|
||||
pen.SetLineJoin(gd::LineJoinRound);
|
||||
pen.SetStartCap(gd::LineCapRound);
|
||||
pen.SetEndCap(gd::LineCapRound);
|
||||
// 悬停高亮(用户 2026-10-30 要求):先沿同一批顶点画一遍**加粗的半透明光晕**,
|
||||
// 再把曲线本身画粗一点。光晕垫在曲线下面 ⇒ 不遮蔽曲线本身、也不影响它的颜色。
|
||||
if (highlight)
|
||||
{
|
||||
gd::Pen halo(ToGdiColor(WithAlpha(derived, 0x55u)),
|
||||
std::max(1.0f, resolved.style->lineWidth) * m_scale + 5.0f * m_scale);
|
||||
halo.SetLineJoin(gd::LineJoinRound);
|
||||
halo.SetStartCap(gd::LineCapRound);
|
||||
halo.SetEndCap(gd::LineCapRound);
|
||||
for (std::size_t k = 0; k < breaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = breaks[k];
|
||||
const std::size_t stop = (k + 1 < breaks.size()) ? breaks[k + 1] : points.size();
|
||||
if (stop <= start + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
g.DrawLines(&halo, points.data() + start, static_cast<INT>(stop - start));
|
||||
}
|
||||
pen.SetWidth(std::max(1.0f, resolved.style->lineWidth) * m_scale + 1.6f * m_scale);
|
||||
}
|
||||
for (std::size_t k = 0; k < breaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = breaks[k];
|
||||
@@ -3287,7 +3312,8 @@ namespace jdi
|
||||
}
|
||||
|
||||
void PlotView::PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int points, int iterations) const
|
||||
const LaneVisual& lane, int points, int iterations,
|
||||
bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -3305,6 +3331,10 @@ namespace jdi
|
||||
// **逐位一致**(用户明确要求"预览显示效果要和构建为新信号一致")。
|
||||
// 整段窗口超过异步门槛时由后台线程计算,绘制线程本帧用已算好的旧曲线顶替,
|
||||
// 因此不会阻塞交互;小信号同步计算。
|
||||
// ⚠️ 「均值点数」的下限是 `kMeanFilterPointsMin`(=1,用户 2026-10-30 明确要求不能为 0):
|
||||
// 即使条目里的值被外部写成了 0/负数,预览也不能按“不平滑”画出来。
|
||||
points = std::max(kMeanFilterPointsMin, points);
|
||||
iterations = std::max(kMeanFilterIterationsMin, iterations);
|
||||
std::size_t spanBegin = 0;
|
||||
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::MEAN_FILTER, *resolved.data,
|
||||
points, iterations, 0, 0, count, spanBegin, true);
|
||||
@@ -3312,11 +3342,12 @@ namespace jdi
|
||||
{
|
||||
return;
|
||||
}
|
||||
PaintDerivedCurve(g, resolved, lane, *curve, spanBegin);
|
||||
PaintDerivedCurve(g, resolved, lane, *curve, spanBegin, highlight);
|
||||
}
|
||||
|
||||
void PlotView::PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int amountPercent, int iterations) const
|
||||
const LaneVisual& lane, int amountPercent, int iterations,
|
||||
bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -4368,8 +4399,12 @@ namespace jdi
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
const bool freeMarker = (kind == EdgePanelKind::MARK);
|
||||
// 「均值滤波」与「弱峰调整」共用「逐条目 + 底部按钮」排布。
|
||||
const bool itemized = (centerAxis || meanFilter || weakPeak);
|
||||
// ⚠️ `itemized` 只表示**旧的逐条目排布**(中心轴 / 弱峰调整)。
|
||||
// 「均值滤波」自 2026-10-30 起有自己的**可滚动分支**(`if (meanFilter)` 内),
|
||||
// 若它仍被算作 `itemized`,旧分支会**再画一遍**每行的名称 + 复选框 ——
|
||||
// 且旧分支**没有任何裁切**,滚动时这些元素就会画到标题栏上
|
||||
//(用户 2026-10-30 报告“元素超出浮动面板”的根因)。
|
||||
const bool itemized = (centerAxis || weakPeak);
|
||||
const bool hasButton = (meanFilter || weakPeak);
|
||||
{
|
||||
if (index >= count)
|
||||
@@ -4878,10 +4913,17 @@ namespace jdi
|
||||
const Lane::MeanFilterItem* item = tabLane.FindMeanFilterItem(ref);
|
||||
const bool checked = (item != nullptr);
|
||||
|
||||
// 叠底:先铺一层不透明同色再叠 0xE8(与「自由标记」分组栏同一做法),
|
||||
// 这样滚动时下层内容不会透出来。
|
||||
// 内容区已由 `g.SetClip(layout.content)` 裁切(行可以“半截”显示,滚动更顺滑);
|
||||
// 已经完整滚出可视范围的行在上面就 `continue` 了,这里不再额外门控。
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale, signalColor);
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale, WithAlpha(signalColor, 0xE8));
|
||||
// 条目栏悬停:在信号色叠底上再压一层强调色,给出"这一行对应哪条曲线"的提示
|
||||
//(曲线本身的高亮见 `PaintLaneDerivedOverlays`)。
|
||||
if (m_meanRowHoverLane == static_cast<int>(index) && m_meanRowHoverRef == ref)
|
||||
{
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale,
|
||||
WithAlpha(m_theme->accent, 0x2E));
|
||||
}
|
||||
|
||||
// 左侧三角(仅勾选时存在):朝下 = 展开、朝右 = 收起。
|
||||
const gd::RectF& triangle = layout.rowTriangles[rowIndex];
|
||||
@@ -4917,26 +4959,25 @@ namespace jdi
|
||||
const wchar_t* label, int current, int minimum,
|
||||
int maximum, bool active)
|
||||
{
|
||||
if (track.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float labelLeft = track.X - 6.0f * m_scale -
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label);
|
||||
UiText(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, track.Y - 8.0f * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label),
|
||||
track.Height + 16.0f * m_scale),
|
||||
m_theme->textMuted, TextAlign::LEFT);
|
||||
// ⚠️ **必须用 `UiTextClipped`(而不是 `UiText`)**:GDI 文本不受
|
||||
// GDI+ `SetClip` 约束,滚动时标签/数值会画到内容区之外
|
||||
//(用户 2026-10-30 报告"元素超出了浮动面板"的直接原因)。
|
||||
UiTextClipped(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, track.Y - 8.0f * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label),
|
||||
track.Height + 16.0f * m_scale),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
const double ratio =
|
||||
(maximum > minimum)
|
||||
? (static_cast<double>(current - minimum) /
|
||||
static_cast<double>(maximum - minimum))
|
||||
: 0.0;
|
||||
UiDrawSlider(g, track, ratio, m_theme->accent, active, m_scale);
|
||||
UiText(g, std::to_wstring(current), m_fonts->monoSmall,
|
||||
gd::RectF(valueRect.X, valueRect.Y, valueColumn, valueRect.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT);
|
||||
UiTextClipped(g, std::to_wstring(current), m_fonts->monoSmall,
|
||||
gd::RectF(valueRect.X, valueRect.Y, valueColumn, valueRect.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT, layout.content);
|
||||
// 复位按钮:悬停高亮 + 与「系数/偏移」菜单同款图标。
|
||||
const bool hot =
|
||||
(m_dragMode != PlotDragMode::EDGE_PANEL_MOVE) &&
|
||||
@@ -4998,6 +5039,18 @@ namespace jdi
|
||||
}
|
||||
|
||||
g.Restore(meanSave);
|
||||
|
||||
// ---- 内容滚动条(只在内容超高时出现;底部按钮不参与滚动)----
|
||||
// ⚠️ 必须在 `g.Restore(meanSave)` **之后**画:滚动条位于内容区右侧、在
|
||||
// 内容裁剪区之外,若在裁剪内画会被整条裁掉(用户 2026-10-30 报告
|
||||
// "全部启用后没有出现滚动条"的根因)。
|
||||
if (layout.scrollTrack.Width > 0.0f)
|
||||
{
|
||||
UiFillRoundRect(g, layout.scrollTrack, layout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, layout.scrollThumb, layout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
}
|
||||
// 「自由标记」面板:按信号分组的**分组栏**(信号色托底 + 三角 + 名称 +
|
||||
// 「N 标记点」+ ✕),下属固定点条目六列对齐(序号 / 取值 / 枚举 / 增量 / 时刻 / 间隔),
|
||||
@@ -5346,9 +5399,10 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (itemized || freeMarker)
|
||||
if (itemized || freeMarker || meanFilter)
|
||||
{
|
||||
// 逐条目绘制已在上面完成(自由标记同样是逐条目排布)。
|
||||
// 逐条目绘制已在上面完成(自由标记同样是逐条目排布,
|
||||
// 均值滤波走自己的可滚动分支)。
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t signalColor = StyleColor(*resolved.style);
|
||||
|
||||
Reference in new issue
Block a user