主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能

① 主题自适应(`Theme.cpp`):新增 `SrgbLuminance` / `ContrastRatio` / `EnsureContrast` /
   `MixToLuminance` / `ThemeLaneBackground`;四套主题的结构色(gridWeak 1.30 / gridStrong 1.65 /
   axisLine 2.10 / borderStrong 1.70)按底色自动校正,Solarised Light 逐位不变;网格叠底改用
   `ThemeLaneBackground()`(4 处),深色主题下网格层与绘图区底色可辨。
② 派生预览曲线(`HighContrastDerived`)重写为**连续**方案:方向沿信号所在侧远离底色,
   目标亮度 = max(与底色对比度 3.2, 信号亮度 ± 0.18),再按亮度平滑混色;调用点传入真实网格底色
   (旧实现默认写死 Solarised Light 底色 ⇒ 深色主题永远选到更暗的那一侧、几乎看不见)。
③ 信号样式区「线宽 / 透明度」两行右侧新增**复位按钮**(`UiPaintResetIcon` 成为唯一实现,
   菜单/弱峰面板/样式区共用同一套笔画);默认线宽统一到 `DefaultSignalLineWidth(finiteCount)`
   (单点信号取 10.0)。按钮右边界与内容列对齐,内容列过窄时整列不显示。
④ 「均值滤波」面板重做:信号色叠底标题栏 + 复选框 + 展开三角 + 「均值点数 / 迭代次数」两行滑块
   (点击数值可改滑块上限、右侧复位)+ 内容区滚动;新增 `MeanFilterItem::{maxPoints,maxIterations,
   collapsed}` 与 `Lane::meanFilterScroll`,缓存键同步补全。
⑤ 浮动面板命中像素缓存时只贴**面板圆角形状之内**的像素(`CreateRoundRectRgn` 裁剪)并按本帧重画投影
   ⇒ 中键移视界后面板周围不再出现一圈旧波形。
⑥ 显示布局边界:每个浮动面板**独占一个绘制层**(按 `m_panelOrder` 递增),绘制侧
   `UiSetTextLayer` 在同一循环内同步推进 ⇒ 下层面板的边框/文字不再画到上层面板上。
⑦ 自由标记:采样候选距离改为**二维欧氏距离**(单点信号不再在整幅宽度内吸住光标),
   纵向光标画在鼠标位置、圆点落在真实采样点(新增 `FreeMarkerProbe::pointX`);
   `MinorCellSparse` 只对胜出信号算一次;折线缓存容量改为 `max(64, 信号数×2)` 且查找改为
   单键哈希 ⇒ MF4 的 285 信号合并网格中 `ProbeFreeMarker` 平均 0.11ms。

验证:`.preflight` 探针重建(`panelcheck` 71/71、`markercheck` 13/13、`ringcheck` 6/6,合计 90 条,
LTO 目标文件上复跑同样全绿);`make release` / `make debug` 零警告;产物 815,104 B。
文档:`agent_mem.md` 第十六轮(含补丁脚本与编码/行尾踩坑)。
This commit is contained in:
DeepSeek-V4.1-Flash committed 2026-10-30 03:10:51 +08:00
1 parent 71195c4c48
commit bdd703b88b
24 files changed
+1815 -256

No files matched your search

+153
View File
@@ -8611,3 +8611,156 @@ PlotView:0 = 网格/图例/Y轴标尺/GX/时间轴/光标读数与取值胶囊
并把面板位置改到胶囊上方(`lane.freeMarkerPanel.x/y`)。
* `lbpanel` 的 "layer 1 只能是面板内容" 断言会随层号约定变化而失效:现在滚动条/回到最上方
在 `base + 2`,因此各组件必须按 `kLayersPerComponent = 3` 取层。
## 2026-10-30 第十六轮(本机):主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能
> 用户本轮七件事:① 四个主题里除 Solarised Light 外,网格叠底 / 时间轴大格与五等分线 /
> 派生预览曲线在深色主题下看不清 ⇒ 结构线要按**背景色**自适应,预览曲线要随信号色**线性**找到
> 高对比位置(旧实现在两个极端色之间"黑白相"跳变);② 信号样式区「线宽 / 透明度」两行右侧
> 加**复位按钮**(与"系数/偏移"菜单的还原图标同款,右边界对齐内容列,单点信号与普通信号默认线宽不同);
> ③ 「均值滤波」面板改成「信号色叠底标题栏 + 复选框 + 展开三角 + 两行滑块 + 复位 + 滚动」;
> ④ 中键移视界时浮动面板周围出现一圈旧波形像素;⑤ 显示布局边界时下层浮动面板的边框画到上层;
> ⑥ 网格里同时有单点信号与常规信号时,光标被单点"吸住";⑦ 上百信号合并到一个网格时自由标记很卡。
### 0. 仓库约定变动
* `.preflight/`(本机离屏探针)已按要求**重建**:`perf/build.ps1` + `perf/probe/*.cpp`。
⚠️ `build.ps1` 链接的是 `Build/Release/*.o`;**LTO=1 构建后必须加 `-Lto`**(补
`-flto -flto-partition=one`),否则链接失败。
* 新增三个补丁脚本(纯 ASCII,配合 UTF-8 数据文件):`patch_insert.ps1`(在匹配行之后插入)、
`patch_range.ps1`(替换行区间)、`patch_line.ps1`(替换单行)。**用途见 §9**。
### 1. 主题自适应:结构线按底色自动校正(`Theme.cpp`)
新增三个**唯一实现**的调色工具(`Theme.hpp`):
| 函数 | 语义 |
| --- | --- |
| `SrgbLuminance(argb)` | WCAG 相对亮度(sRGB 线性化 + 0.2126/0.7152/0.0722) |
| `ContrastRatio(a, b)` | `(L1+0.05)/(L2+0.05)`,1…21 |
| `EnsureContrast(bg, fg, minRatio)` | 不足阈值时沿"远离底色"方向**二分解出最小混色量**;**已达标逐位返回原色** |
| `MixToLuminance(color, target)` | 把颜色沿亮度轴平滑推到目标相对亮度(保持色相) |
| `ThemeLaneBackground(theme)` | 网格叠底 = `EnsureContrast(plotBg, cardBg, 1.03)` |
四个内置主题都过一遍 `NormalizePlotContrast()`(阈值取"Solarised Light 现有值刚好达标"的水位):
| 字段 | 阈值 | Solarised Light 现状 |
| --- | ---: | ---: |
| `gridWeak`(网格主刻线 + 五等分线基色) | 1.30 | 1.38 |
| `gridStrong`(零线/加重线) | 1.65 | 1.80 |
| `axisLine`(时间轴 / 分界线 / 绘图区外框) | 2.10 | 2.43 |
| `borderStrong`(网格之间分界线) | 1.70 | 2.43 |
* **Solarised Light 四个值逐位不变**(探针钉住),默认观感零变化;深色主题自动提亮。
* 「网格叠底」改用 `ThemeLaneBackground()`(4 处:整幅重绘 / 宽基底图层 / 样式补丁 / 五等分线混色基底)。
Solarised Dark 的 `cardBg` 与 `plotBg` 本来是同一个 base03 ⇒ 现在被拉开 **1.03**(亮度变化 ≈ 3%),
网格这一层才看得出来。**Solarised Light / Light 也会被同样拉开 1.03**(原本完全同色)——
这是刻意的统一规则,观感变化极小。
### 2. 派生预览曲线:连续、双向可比
`HighContrastDerived(color, background)`(`PlotViewInternal.hpp`)**重写**:
1. 方向 = 信号相对底色的那一侧继续远离(几乎同亮度时按底色明暗选一侧);
2. 候选一 = `EnsureContrast(bg, color, 3.2)`(与底色对比度达标),候选二 = 信号亮度 ± 0.18,
取**更远离底色**者;3. `MixToLuminance` 平滑推到该亮度。
两个候选都是输入的连续函数(max/min)⇒ **拖拾色器 / 换预设色 / 切主题时颜色连续变化**,
不再出现旧实现"混 72% 黑还是白、按评分二选一"的**阈值跳变**(探针实测最大亮度跳变 0.012)。
⚠️ 调用点必须传**网格底色**(`ThemeLaneBackground(*m_theme)`)——旧实现的默认背景写死成
Solarised Light 的 `0xFFFDF6E3`,这是"换深色主题后预览曲线永远是更深的那一侧、几乎看不见"的根因。
### 3. 信号样式区「线宽 / 透明度」复位按钮
* `SignalStyle` 默认线的**唯一来源**:`DefaultSignalLineWidth(finiteCount)`
(普通 = `kDefaultSignalLineWidth` 1.0;**单数据点 = `kSinglePointSignalLineWidth` 10.0**)。
`BuildSignalGroup` 的初始化与复位按钮共用它。
* 布局:内容列右端先预留 `kSliderResetSize`(16) + `kSliderResetGap`(4),数值框与轨道依次收缩 ⇒
**按钮右边界 == 内容列右缘**(探针断言误差 < 0.6px)。内容列太窄时整列不显示按钮。
* 图标:`UiPaintResetIcon()`(`PaintUtil`)成为**唯一实现**,
`pmdetail::PaintResetIcon` / `pvdetail::PaintPanelResetIcon` 都改为转调它。
* 命中:`StyleHit::WIDTH_RESET / OPACITY_RESET`(悬停高亮 + 手型 + 双击不解释为其它语义)。
### 4. 「均值滤波」面板重做(与「自由标记」同一套浏览方式)
* 条目 = **信号色叠底标题栏**(左侧展开三角 + 名称 + 右端**复选框**)。勾选前只有标题栏;
勾选后出现三角与两行滑块,三角可收起参数行。复选框自绘(对比色描边/填充),
主题色在信号色叠底上不可辨。
* 两行滑块:`均值点数`(0…上限)与 `迭代次数`(1…上限),**标签不带 X / N**;
右侧数值可点击输入**滑块上限**(沿用"输入上限(当前N)"提示与 `EditFilter::BYTE`),
最右侧是复位按钮(点数 → 5 且上限 → 50;迭代 → 1 且上限 → 10)。
新增 `Lane::MeanFilterItem::{maxPoints, maxIterations, collapsed}`、`Lane::meanFilterScroll`。
* 内容区超高时**滚动**(`layout.content` + 滚动条 + 滚轮),底部「构建为新信号」按钮固定。
面板高度上限 = 绘图区可用高度 ⇒ 上百信号合并时不再顶出屏幕。
* 命中要求"行**完全落在内容区**里",避免滚动出的行抢底部按钮的点击。
* 缓存键:`PanelSlotKey` 增补 `rowHeaders / rowTriangles / scrollOffset / MarkerGroup::check /
meanItem->maxPoints / maxIterations / collapsed`;`meanFilterScroll` **只进槽键**(不进全局键,
否则滚动一个面板会让所有面板重画)。
### 5. 浮动面板周围那一圈旧波形(中键移视界)
**根因**:面板像素缓存按 `panel ± 4px` 取块,块的边缘是**上一帧的场景**;
`PanelGlobalKey` 刻意不含视界/幅值(否则平移会重画全部面板)⇒ 整块贴回时把旧波形"复印"了一圈。
**修法**:命中缓存时
1. 先按本帧重画**投影**(它在圆角形状之外,不能靠缓存);
2. 用 `CreateRoundRectRgn` + `ExtSelectClipRgn(RGN_AND)` 裁成**面板圆角形状**,
只 BitBlt **面板本体**那一块 ⇒ 圆角之外仍是本帧场景。
探针 `ringcheck`:同一视界下「走面板缓存」与「强制整帧重绘」在面板外扩 10px 环带上
**逐像素 0 差异**(23850 像素),并且断言这一帧**确实命中了面板缓存**(否则判据空洞)。
### 6. 显示布局边界:每个浮动面板独占一层
**根因**:所有面板都被收集在同一层 `baseLayer + 1`,同层内只能按 `depth` 排序 ⇒
下层面板的行/文本(depth 4)画在**上层**面板本体(depth 2/3)之上。
**修法**:按 `m_panelOrder`(底 → 顶)**逐面板 +1 层**,并让绘制侧的
`UiSetTextLayer` 在 `PaintEdgeCursor` 里**同一个循环内同步推进**(`panelTextLayer + 序号`),
函数返回前把层号推到最上面板之上(滚动条 / 回到最上方 / 输入框用它的下一层)。
`BuildBoxes` 与绘制**同源**:都以 `m_panelOrder` 的顺序 + 同一公式算层号。
探针断言:两个完全重叠的面板,其遮挡矩形的层号严格递增,且上层有自己的 box 集合。
### 7. 自由标记:选取规则与性能
* **选取距离改为二维**:采样候选 = 鼠标到"该采样点屏幕位置"的**欧氏距离**(旧实现只看纵向 ⇒
单数据点只要高度接近就在**整幅宽度**内胜出,无法标注其它信号);曲线候选仍是到折线的垂距
(阶跃竖直段靠它命中)。
* **纵向光标画在鼠标位置**(`probe.x = 鼠标 x`),数据点圆点/吸附环落在**真实采样点**
(新增 `FreeMarkerProbe::pointX = TimeToX(time)`)。稀疏信号下两者分开是刻意的。
* **稀疏判定只在胜出信号上算一次**(`MinorCellSparse` 每信号 50 格 × 2 次二分,
上百信号时是纯浪费 —— 它只决定要不要画吸附环)。
* **折线缓存两处根治**(上百信号合并网格时的卡顿主因):
① 容量从固定 64 改为 `max(64, 全部信号数 × 2)`(`BuildLaneVisuals` 刷新 `m_polylineCacheCapacity`),
否则装满就整表清空 ⇒ 每帧重建全部折线;
② 查找改为比**一个 64 位键哈希**(命中后再校验 ref/采样数/视界),不再逐条比较十几个字段。
* 实测:MF4-Record 的 **285 个信号合并到一个网格**,`ProbeFreeMarker` 平均 **0.11ms**(探针上限 8ms)。
### 8. 验证
```
panelcheck : 71/71 (主题对比度 24 条、样式区复位 7 条、均值面板 12 条、调试层序 4 条…)
markercheck: 13/13 (单点/常规共存的选取 6 条 + 285 信号探测耗时)
ringcheck : 6/6 (面板缓存环带 0 差异 + 缓存确实命中)
合计 90 条断言全绿(LTO 目标文件上复跑同样全绿)
make release -> 零 error 零 warning;JustDataInspector.exe 815,104 B(+23,552 B ≈ +2.9%)
make debug -> 零 error 零 warning
```
### 9. 踩坑(新增)
* ⚠️⚠️ **源文件里的中文不能用编辑工具"凭记忆"当匹配串**:本仓库若干注释里存在
"重复滤波次数 ,至少"这类**多一个空格**的文本,肉眼与 `read_file` 都不易察觉,
匹配必然失败。可靠做法:先按字节导出该行码点确认,或改用
`.preflight/patch_{insert,range,line}.ps1`(纯 ASCII + UTF-8 数据文件 + **按行号**改动)。
* ⚠️ 用 .NET `WriteAllText` 重写文件会把 **CRLF 全变成 LF** ⇒ `git` 提示
"LF will be replaced by CRLF",整文件变成 4000 行 diff。改完必须把行尾恢复成 CRLF。
* ⚠️ PowerShell 的 `$Matches` 是正则匹配自动变量(哈希表),在脚本里给
`$Matches += $i` 会报"只能将哈希表添加到另一个哈希表中" ⇒ 换名 `$hits`。
* ⚠️ `Start-Process -ArgumentList` 拼接的参数**不会自动加引号**:本工程路径含空格,
`-I<路径>` 与 `.o` 路径都必须手工包 `"`。
* `PlotView::EdgePanelLayout` / `FreeMarkerProbe` / `LaneVisual` 是**嵌套类型**,探针里必须写
`PlotView::` 前缀。
* 探针的工程根目录推导:探针若放在 `.preflight\xxx.exe`,从 exe 路径**上溯 2 级**即根目录
(上溯 3 级会跑到桌面上,`TestData` 找不到)。