71195c4c482c6cb61c2d8654f6a107c9a94d0743
73
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
71195c4c48 |
新增主题配色(4 套)+ 信号右键菜单精简与「克隆该信号」+ 布局边界层级根治 + 单点信号吸附修复
## 为什么
用户 2026-10-29 报告/要求五件事:
1. 派生信号的「时间轴偏移保留在样式上、其它调整烘值」维持现状(确认无需改动);
2. 信号名右键菜单里很多功能已被下方样式自定义区覆盖 ⇒ 只保留「仅显示该信号」,
并在它**上方**新增「克隆该信号」;
3. 设置菜单新增「主题配色」二级菜单(带 √ 状态),顺序 Light / Solarised Light /
Dark / Solarised Dark,默认主题维持现状;
4. 布局边界:中键平移视界时浮动面板上的边界**闪烁**;光标取值胶囊的**文字**框会**穿透**
到浮动面板上;并请核对网格内固定构件(图例 / Y 轴标尺 / GX 标签 / 回到最上方 /
浮动面板)的层级关系;
5. 单数据点信号在拖动光标时的**吸附**功能消失。
## 根因
### 布局边界层级(两个都很隐蔽)
- **文本层号写死**:文本是在绘制过程中由 `UiTextRectSink` 收集的,而宿主把所有文本都记在
固定层 2;组件几何层却是"组件表下标"(`PlotView` = 0)。面板层 = PlotView 层 + 1 = 1,
**低于**文本层 2 ⇒ 面板的遮挡矩形挖不到文本 ⇒ 胶囊文字画在面板表面。
- **面板像素缓存**:面板"先画进图层、命中缓存就贴图",贴图那一帧不画字 ⇒ 文本框凭空消失
⇒ 面板上的边界闪烁。
- 顺带发现 `PlotView::BuildBoxes` 用 `PushLayer()` 却**从不复位**,会把宿主后续所有组件的
层号整体推高。
### 单点信号吸附
`MinorCellSparse` 在"把判定范围收窄到数据实际时间跨度"的分支里有
`if (!(dataMax > dataMin)) return false;`。单个有效采样点时 `dataMax == dataMin`,
于是被判定成"不稀疏" ⇒ 不吸附。
## 改了什么
### 1. 布局边界层级
- `UiTextRectSink` 新增 `OnTextLayer(int)` / `TextLayer()`;新增 `UiSetTextLayer` /
`UiGetTextLayer`;`UiBoxTable` 实现为改 `m_layer`,并暴露 `Layer()`。
- `Application::Render` 在绘制每个组件前下发 `UiSetTextLayer(ComponentLayer(&component))`,
浮层用 `OverlayLayer(1/2)`;新增 `ComponentLayer()` / `OverlayLayer()` /
`kLayersPerComponent = 3`,与 `PaintLayoutBounds` 的层号**完全同源**。
- `PaintLayoutBounds` 改为**显式** `SetLayer(下标 × 3)`(不再依赖 `PushLayer` 累加)。
- `PlotView`:`BuildBoxes` 用 `baseLayer + 1` 放浮动面板(并登记遮挡)、
`baseLayer + 2` 放绘制在面板之上的滚动条 / 回到最上方 / 输入框,结束**复位**层号;
`paintOverlays` 同步分两次抬高文本层。
- 面板像素图层新增 `PanelSlot::textRects` + 转发型收集器 `PanelTextCollector`:
渲染时存文本框,命中缓存贴图时回放 ⇒ 几何与缓存命中无关(消除闪烁)。
### 2. 信号右键菜单 + 克隆
- 菜单只剩:标题、**克隆该信号**、**仅显示该信号**;删除显示/隐藏、显示全部/隐藏全部、
预设色块行、样式三项,以及对应的 `CMD_SIGNAL_TOGGLE/SHOW_ALL/HIDE_ALL`、
`CMD_COLOR_BASE`、`CMD_MODE_BASE` 及其分支(死代码)。
- 新增 `CMD_SIGNAL_CLONE`(post `WM_APP + 5`)→ `Application::CloneSignal`:
把「调整」(系数 / 绝对值 / 镜像 / Y 偏移)烘进采样值、**时间轴偏移保留在样式上**,
名称前置 `*`,插入到源信号**正下方**,并**保留原外观**(颜色/透明度/线宽/绘制样式)。
- 抽出 `Application::InsertSignalBelowSource(...)` 供派生与克隆共用。
### 3. 主题配色
- `ThemeKind{LIGHT, SOLARIZED_LIGHT, DARK, SOLARIZED_DARK}` + `ThemeFor()` / `ThemeName()`;
Light 取 VS Code 2026 默认浅色、Dark 取 Dark+、Solarized Dark 取 VS Code solarized dark。
**默认仍是 Solarised Light**。
- 新增 `CurrentTheme()` / `SetCurrentTheme()`:宿主交出自己的 `m_theme` 地址,
切换主题只改内容;`UiControls` 里没有 `Theme` 形参的原语改用它(原来硬编码
`SolarizedLight()`,换主题不生效)。
- `Application::ApplyTheme()`:改配色 → `PlotView::OnThemeChanged()`(作废场景/宽基底/
装饰/光晕/面板图层)+ `SignalPanel::OnThemeChanged()`(作废行位图)→ 整窗重绘。
- 菜单用 `SUBMENU` + 4 个 `keepOpen` 子项;新增 `PopupMenu::UpdateSubmenuItemLabel()`
回写勾选,并让子菜单分支尊重 `keepOpen`(原为无条件关闭)。
### 4. 单点信号吸附
`MinorCellSparse` 改为只在 `dataMax < dataMin` 时放弃;相等时按"那一个小格"判定
(`need = 5 > finiteCount = 1` ⇒ 稀疏 ⇒ 吸附恢复)。
### 5. 审计
`Makefile` 的 `LAYOUT_BOUNDS` / `JDI_LAYOUT_BOUNDS` **从未被任何 `#if` 引用**
(`LAYOUT_BOUNDS=0` 产物字节数完全相同),属名实不符的死开关 ⇒ 删除并注明原因。
## 怎么验证
新增两支离屏探针(不占鼠标 / 键盘 / 屏幕):
```
.preflight/round15check 28 passed, 0 failed
单点吸附 7 条(含"远处不吸附")、面板层级 5 条、面板文本框跨帧与中键平移稳定 2 条、
克隆 14 条(烘值逐点比对 / 名称前缀 / 偏移与外观保留 / 调整复位)
.preflight/themecheck 17 passed, 0 failed
4 套主题可读性与可区分、默认 = Solarised Light、逐主题换色且无旧底色残留、
切回默认逐像素还原(diff = 0)、子菜单 4 项 / 顺序 / 唯一勾选 / 命令切换 / 菜单保持打开
```
回归全绿:
```
uicheck 69/69 lbcheck 21/21 lbresid 5/5 lbzorder 4/4
lbcovr 7/7 lbpanel 10/10 derivecheck 16/16
rendercheck: 8 个场景哈希与上一轮逐一相同 + FUNC 7/7, exit=0
A FAED59DF284153DB B E6443BFE4E3955F8 C 96A0AA8C402F1729 D E3D5DBD998E43720
E DA0583CB13C26A6F F FFB822159F604D1D G 19F8DE8FE3A8DF18 H DBBDDE4CB2C97C92
```
`make release` / `make debug` 零 error / 零 warning;产物 **791,552 B**
(单一 exe,未拆 DLL,未改 `-Oz`;离 1MB 预警线约 250 KB)。
|
||
|
|
044b87383b |
体积 L3(-fno-exceptions + MemProbe + LTO)与 L1 几何产物层;产物 909,824 -> 788,992 B
## 为什么
用户 2026-10-29 要求:按既定计划完整实现改进,但
① **不得把编译产物拆成 DLL**(项目定位是单文件轻量工具,最终只能有一个 .exe);
② 异常处理改用**预检验**而不是 try/catch(-fno-exceptions 省体积,且 try/catch 本身开销大);
③ 先删除 Documents 内三份 UI 重构文档再按"计划 / 当前进度 / 最终报告"重建;
④ 顺便审计代码,删除无用/过时注释,修复名实不符;
⑤ 提交到本地仓库,期间不要占用用户的鼠标/键盘/屏幕。
## 改了什么
### 1. 体积(909,824 B -> 788,992 B,-120,832 B,-13.3%)
Makefile 新增三个可覆盖开关(默认全为 1):
- `LAYOUT_BOUNDS` 编入/剔除「显示布局边界」叠加层
- `NO_EXCEPTIONS` 追加 `-fno-exceptions`
- `LTO` 追加 `-flto`(编译 + 链接)
派生:`FUNC_SECTIONS`(LTO 下为空)、`GC_SECTIONS`(`-Wl,--gc-sections`)、
`ASM_LISTING`/`ASM_LISTING_C`(LTO 下为空,因为 lto-wrapper 会丢弃 `-Wa` 清单)。
两个实测复现的坑(已写进 Makefile 注释):
- **切换 LTO 必须 `make clean`**:LTO 与非 LTO 目标文件混链会让 GCC 15.2.0 在
`binds_to_current_def_p (symtab.cc:2598)` 处 ICE。
- **`-flto` 与 `-ffunction-sections -fdata-sections` 同时打开也 ICE**(干净重编也必崩,
报错落点是 `DataModel.hpp:393`,但那是 COMDAT 解析落点、不是原因)⇒
LTO 下 `FUNC_SECTIONS` 置空,并加 `-flto-partition=one`:默认 11 个分区的 LTRANS
稳定复现,单分区从未复现。**这是稳定性选项,不是性能选项。**
### 2. 关异常必须成对交付:预检验 + 兜底
新增 `Utilities/Inc/MemProbe.hpp` + `Utilities/Src/MemProbe.cpp`:
- `MemAvailablePhysical()`:`GlobalMemoryStatusEx` + 200ms 缓存
- `MemCanAllocate(bytes)`:预留后仍须保有 1/8 可用内存
- `MemReserveIfRoom(container, n)`:含 `capacity()` 早退与乘法溢出保护
- `MemInstallTerminateHandler()`:OOM 时弹一次带建议的模态提示后 `_Exit`(而不是静默消失)
接入:
- `Utilities/Src/DataModel.cpp`:全仓**唯一**的 `try/catch`(`extremes.reserve` 捕
`std::bad_alloc`)改为 `MemReserveIfRoom(extremes, count / 8)`
- `Components/PlotView/Src/PlotView.cpp`:`AvailablePhysicalBytes()` 原有一份**完整重复**
的 `GlobalMemoryStatusEx` + 缓存实现 → 转发 `MemAvailablePhysical()`
- `Components/Application/Src/Application.cpp`:`Initialize()` 开头安装 terminate handler
### 3. L1 几何产物层
新增 `Components/Shared/Inc/UiBox.hpp` + `Src/UiBox.cpp`:
- `struct UiBox { rect, clip, depth, layer }` —— 纯值类型,不继承任何东西
(继承会和 PlotView 4 层位图缓存 / SignalPanel 行位图缓存的失效体系打架)
- `class UiBoxTable : public UiTextRectSink` —— 只累计几何 + 裁剪/层/遮挡,**不含绘制代码**
`UiComponent::CollectDebugBoxes` → **`virtual void BuildBoxes(UiBoxTable&)`**:
组件从"收集布局边界(调试用)"改为"上报几何产物",不再需要知道"调试"这个概念存在。
`UiDebugBounds.hpp/.cpp` 退化为唯一的消费者(配色 + 包角描边 + 遮挡挖洞)。
除宿主 `ApplicationCommands.cpp` 外,所有组件源码现在只包含 `UiBox.hpp`。
### 4. 审计
- `MemProbe` 里"省 40~60KB"的估算 → 改为实测 27,136 B
- `MemProbe.hpp` 的 `<new>`、`MemProbe.cpp` 的 `<cstdio>`:未使用,删除
- `UiDebugPaintBounds(g, rect, depth)`:零调用者,删除(只保留带 clip 的版本)
- `TabBar`/`Toolbar`/`PlotView`/`SignalPanel` 的 `BuildBoxes` 文档"(调试用)"→"几何 box"
- `PlotView::SetForceFullRepaint` 补写"为什么保留而不优化"
### 5. 文档
`Documents/UI_Refactor_Plan.md` / `_Progress.md` / `_Report.md` 先 `git rm` 再按
「计划 / 当前进度 / 最终报告」重建;`Documents/agent_mem.md` 追加第十三轮记录。
## 怎么验证
```
make clean
make release TARGET_FILE_NAME=JustDataInspector -j16 # 零 error / 零 warning,788,992 B
make debug TARGET_FILE_NAME=JustDataInspectorD -j16 # 零 warning
uicheck 69/69 lbcheck 21/21 lbresid 5/5 lbzorder 4/4 lbcovr 7/7 lbpanel 10/10
rendercheck: 8 个场景哈希与改造前逐一相同 + FUNC 7/7 PASS, exit=0
A FAED59DF284153DB B E6443BFE4E3955F8 C 96A0AA8C402F1729 D E3D5DBD998E43720
E DA0583CB13C26A6F F FFB822159F604D1D G 19F8DE8FE3A8DF18 H DBBDDE4CB2C97C92
```
体积溯源(`objdump -h`,基线 → 最终):
`.text` 735,600 → 657,168;`.rdata` 49,184 → 35,016;
`.xdata` 48,132 → 27,512;`.pdata` 35,424 → 28,524;`.rsrc` 20,656 不变。
## 没做什么(有意为之)
- **没有拆 DLL**(用户明确要求单文件)
- **没有改优化等级**(`-Oz` 保持)
- **L2(`UiDamage` 脏矩形 + 独立叠加层)评估后缓做**:受益方只有"调试开关打开时的帧率",
而正确实现必须把叠加层旧矩形注入底层组件重绘范围,穿透 PlotView 四条快路径 + 两层缓存;
在用户反复报过"拖拽错位 / 贴图残留"的背景下,风险与收益不成比例。理由写在
`Documents/UI_Refactor_Plan.md` §5 与 `PlotView::SetForceFullRepaint` 的文档里。
|
||
|
|
aa4925a2b7 |
fix(UI): 边界框不再内缩 + 文本获得几何身份 + 补全漏项 + 描边提速 32%
## 1. 取消内缩(这是"边框不清晰"的主因)
第九轮为"父子矩形共边不叠粗"给 depth > 0 的 box 加了 1px 内缩。结果 GX 徽标
(30x17 逻辑像素的胶囊,圆角 = 高的一半,外接矩形正好等于 LaneBadgeRect)内缩 1px 后
框比形状小一圈 ⇒ 左右尖端与上下圆弧全部落在框外。
现行规则:**框 = 可见形状的外接矩形,绝不内缩**。父子共边时让"后画的覆盖"即可,
这本来就是正确的图层语义。层级 > 0 只影响配色,不再影响几何。
## 2. 文本第一次有了几何身份(唯一出口回报,不逐处登记)
文本是本项目唯一"没有几何身份"的元素:65 处绘制点各自现算 gd::RectF、画完即弃。
逐处补登记必漏,因此在唯一出口回报一次:
PaintUtil 新增
class UiTextRectSink { virtual void OnTextRect(const gd::RectF&) = 0; };
void UiSetTextRectSink(UiTextRectSink*); // 开关关闭时为 nullptr
void UiSetTextRectOffset(float dx, float dy); // 离屏位图渲染的坐标换算
UiTextRectSink* UiGetTextRectSink();
DrawUiText() / UiTextAtClipped() 画完后按对齐方式 + 实测宽高把布局矩形**收窄成墨迹矩形**
再回报。UiDebugBoxList 直接继承该接口,宿主只需 UiSetTextRectSink(&m_debugBoxes) 一句。
开关关闭时只是一次空指针判断,正常绘制零影响。
## 3. 缓存命中的行也要有文本框
面板的行是"先画进位图再贴回",命中缓存的帧**根本不调用 UiText** ⇒ 文本框第二帧起消失。
做法:行渲染时用本地 RowTextCollector 收集(位图坐标)→ 连同位图存进
RowBitmapCache::textRects → 贴图时按 (originX, originY) 回放。
⚠️ **必须无条件收集**:只在"外层有收集器"时收集会导致"关调试时滚过列表 → 打开调试 →
命中缓存 → 永远没有文本框"。
## 4. 补齐 PlotView 漏项(全部读现成成员)
m_timeAxisRect / m_scrollBarRect / BackToTopRect() / m_cursorSpanRect /
m_cursorLabelRects / EditFieldRect()
## 5. 描边提速:图元 12 → 8
微基准结论:图元数量是唯一瓶颈(每次 FillRectangle ≈1µs)。
旧实现把每条边拆成"中段 1px + 两端各一段加粗"= 12 次;新实现 = 4 条 1px 边 + 4 个自适应角块。
实测 paintBoxes 5.892ms → 4.028ms(-32%),观感几乎一致(角仍是两条粗边的交点)。
⚠️ 不要改写成 GraphicsPath/FillPath(单次约 20µs,比 12 段还慢);
GDI PolyPolygon 一次画 5688 个矩形要 114ms,更不能用。
## 6. 关闭开关时清空容器(释放内存,避免误读)
## 7. 验证
- make release / make debug 零 error 零 warning;TargetCheck.exe 909,824 B(888.5 KiB < 900 KiB)。
- 新增探针 .preflight/lbcovr 7/7:box-time-axis / box-plot-scrollbar / box-back-to-top /
box-gx-badge-matches-pill(容差 0.5px)/ text-boxes-collected(89 个)/
row-text-boxes-from-cached-rows / boxes-cleared-when-off。
- 回归:lbcheck 21/21、lbresid 5/5、lbzorder 4/4、uicheck 69/69。
- rendercheck:8 个场景的整帧与区域哈希与上一轮**逐一相同** + FUNC 7/7
(证明调试功能对正常绘制零影响)。
- 帧耗时分解(MF4 / 283 网格):base 9.06ms、仅强制整帧 6.66ms、完整调试 11.35ms;
collect 0.082ms、paintBoxes 4.028ms(上一轮 5.892ms)。
## 8. 文档
Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第九阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第十一轮」硬约束与踩坑。
|
||
|
|
57e12cfb27 |
fix(UI): 布局边界按尺寸自适应 + 修复拖滚动条的绘制残留与浮层层级
## 1. 边界描边按元素尺寸自适应(修复"小元素变成粗描边")
旧规则四角固定 3-4px:对 15px 的复选框来说包角占短边 20%,看起来就是一整圈粗边框。
新规则全部以**短边**为基准:
- 四边固定 1 设备像素;
- 包角粗细 = min(基础 4/3, 短边/10),再夹到 [1, 短边/4];
- 包角长度 = min(14, 短边/3),且不小于包角粗细;
- 短边 <= 12 只画四个 L 形角标记、**不画边**(一圈连续描边会让小方框显得"被框住");
- 短边 <= 5 完全不画(再画就盖住元素本身);
- 内缩只用于「层级 > 0 且短边 >= 10」,否则小元素会被缩没。
同时**删除文字标签**(含 UiDebugPaintStyle):黑底白字在小元素上必糊成一片,体积也省约 1.5KB。
## 2. 拖网格滚动条时的"大量绘制残留":三个独立成因
R1. PlotView::CollectDebugBoxes 读了 m_decorRects —— 那是**装饰层位图缓存**的登记矩形,
坐标处在位图/图层坐标系(带负 Y、超出绘图区),于是出现 (472,-27 1213x101) 这类
**横跨工具栏的幽灵巨框**。改为只用 LaneBadgeRect 与 m_legendHits(屏幕坐标)。
R2. Paint 的三条快路径(静态场景贴图 / 宽基底贴图 / 样式补丁)只重画**变化的部分**,
而调试边框是画在整个窗口上的叠加内容 ⇒ 上一帧的边框残留在未被重画的像素上,
直到松开鼠标走整幅重绘才消失。
新增 PlotView::SetForceFullRepaint(bool):开启调试叠加层时禁用全部快路径。
R3. 组件内元素可能落在自己地盘之外(滚出可视区的 lane.rect 的 Y 为负),不裁剪就画到
工具栏/标签栏上。box 增加 clip:宿主按组件 Area() 设置,Add() 跳过无交集、
绘制时与 clip 求交。
另外把 Render 的 fullChrome 并入 m_showLayoutBounds,装饰区也保证完整一帧。
## 3. 层级:排序 + 挖洞(修复二级菜单被下层边界覆盖)
- UiDebugBoxList::PushLayer() + 按 (layer, depth) 稳定排序:后收集的内容压住先收集的。
- 但仅靠排序不够:调试线画在所有内容之后,下层组件的线仍会显示在不透明菜单上面。
新增 AddOccluder():菜单/拾色器把本体登记为遮挡矩形,绘制**更低层**的 box 时用
Region::Exclude 挖洞。实测菜单区与子菜单区的琥珀/绿色像素均为 0。
- 浮层**不设** clip = 本体矩形:二级子菜单会展开到本体之外,设裁剪会把它整个裁掉。
## 4. 验证
- make release / make debug 零 error 零 warning;TargetCheck.exe 906,752 B(885.5 KiB < 900 KiB)。
- 新增 .preflight/lbresid(MF4-Record.MF4 / 283 网格 / 拖滚动条)**5/5**:
toolbar-unchanged-while-scrolling、repaint-same-state-is-identical、
toolbar-restored-by-full-repaint、residual-never-above-plot-area、later-layer-wins-over-earlier。
- 新增 .preflight/lbzorder **4/4**:菜单与二级子菜单区内下层颜色像素 = 0。
- 回归:.preflight/lbcheck 21/21、Tools/uicheck 69/69、
Tools/rendercheck「工具栏带以下」区域哈希 8/8 与上一轮逐一相同 + FUNC 7/7。
注:判断"是否有绘制残留"不能用"拖拽帧 vs 松开后一帧"——两者图层预热进度不同会产生
几十万像素的**合法**差异;正确判据是**同一状态连续两帧必须逐像素相同**。
## 5. 文档
Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第八阶段;
Documents/agent_mem.md 同步变更日志与「2026-10-28 第十轮」硬约束与踩坑。
|
||
|
|
ab19a6e72e |
feat(UI): 显示布局边界(Android 风格)+ 工具栏设置菜单(齿轮)+ 三处可见收益优化
## 1. 新功能:齿轮按钮 -> 应用设置菜单 -> 显示布局边界 - ToolbarAction::SETTINGS 位于「导入文件」左侧;工具栏布局改为 「左侧固定簇(kLeftClusterCount = 2:齿轮 + 导入文件)+ 右侧按钮组」。 - 齿轮是矢量化绘制(8 齿 + 外环 + 轴孔,全部按 half 的比例推导), 不用字体字符「⚙」——该字符在不同回退字体里大小与基线不一致,会忽大忽小、垂直不居中。 UiButtonWidget 新增 UiButtonIcon(NONE / GEAR)与 SetIcon。 - MenuScope::APP_SETTINGS(新增)+ 设置菜单:HEADER「调试」+ 勾选切换项「显示布局边界」 (keepOpen:点击后菜单保持打开,勾选态由 UpdateItemLabel 回写)。 锚点取 Toolbar::ButtonRect(SETTINGS) 的控件实测矩形,宿主不再自己推算按钮几何。 ## 2. 布局边界:基类给协议,组件自己上报 - UiComponent 新增 virtual PaintDebugBounds(g, scale),默认只画 Area()。 - Toolbar / TabBar 覆盖:跳过与区域重合的根控件,遍历 m_root->Children()(层级 1); TabBar 另上报每个 ✕(层级 2)。 - SignalPanel 覆盖:表头 / 筛选框 / 列表 / 样式区(1)+ 滚动条、样式行、当前可见列表行(2)。 - PlotView 覆盖:每个 LaneVisual::rect(1);不上报随数据变化的瞬态几何(曲线/胶囊/提示)。 - UiWidget 新增 PaintDebugBounds(g, scale, depth) 递归整棵控件树 —— 已上树的控件零成本获得边界。 - 宿主 PaintLayoutBounds 补客户区外框、分隔条、打开的菜单/拾色器。 视觉与实现约束(Android「Show layout bounds」风): - 只描边、不填充(几十个嵌套矩形叠加填充会把内容糊住); - 层级配色 青蓝(2px) / 品红 / 琥珀 / 绿(1px); - 线宽按设备像素固定(不乘 scale),临时 PixelOffsetModeNone + 关抗锯齿 + 整像素对齐, 保证 1px 就是 1px; - ⚠️ 只在 Render 最后一步叠加,绝不进入任何位图缓存(PlotView 4 层缓存 / SignalPanel 行位图缓存), 否则关掉开关后线条会残留; - ⚠️ fullFrame 并入 m_showLayoutBounds:开启时必须整帧回送,否则「只回送绘图区」的快路径 会让装饰区边界停在旧帧上。 ## 3. 可见收益优化(行为不变) - Toolbar::Layout:控件树改为按需重建(m_treeDirty),只在规格/字体/主题/缩放/动态文本 变化时重建。拖动面板分隔条时 Relayout 每帧调用一次 -> 不再每帧 10 次堆分配 + 8 次测量缓存失效。 - Toolbar::SetActionLabel:只写 m_labelOverrides(唯一权威)+ 置树脏,消除「覆盖表与控件文本 双写」可能不同步的问题。 - UiWidget::DispatchRedraw(event):新增;收敛 Toolbar / TabBar 里重复的 Dispatch(); return UiRectHasArea(TakeDirty());(TakeDirty 会清零,漏一次就把下一次真实变化 误判成「无变化」)。 ## 4. 验证 - make release / make debug:零 error 零 warning;TargetCheck.exe 897,024 B(< 900 KB)。 - 新增离屏探针 .preflight/lbcheck:21 / 21 PASS (齿轮在导入文件左侧/同高/更窄;开启后青蓝 24,930 px、品红 19,734 px;关闭后逐像素还原; 菜单 scope、锚定贴合按钮、宽度未被撑大、条目初始未勾选、点击翻转开关、菜单保持打开)。 - Tools/rendercheck:8 个场景的「工具栏带以下区域」哈希与改造前**逐一相同** (证明改动像素被完全限制在工具栏带内);整帧哈希因新增按钮整体变化,属预期。 FUNC 断言 7 / 7 PASS。 - Tools/uicheck:69 passed / 0 failed。 ## 5. 文档 Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第六阶段; Documents/agent_mem.md 同步变更日志与「2026-10-28 第八轮」硬约束与验证坑。 |
||
|
|
0ec8055e95 |
重构(UI): 统一组件协议 + 去手写链路 + 大文件按职责拆分 + 代码审查 + 剥离 Tools
## 1. 新增 UiComponent 顶层组件协议(不是负优化)
Components/Shared/Inc/UiComponent.hpp:纯接口、无状态,8 个方法
SetScale / Layout / Area / Paint / HitTest / OnMouseLeave / IsEditing / QueryCursor。
Toolbar / TabBar / SignalPanel / PlotView 本来就各自拥有完全同名同签名的成员,
因此改造 = 加 override + 补 Area()(PlotView 另加一个非 const 的 QueryCursor 转发,
因为它自己的查询会惰性建立行缓存),实现代码零改动、静态派发、体积无增长。
刻意不把 UiWidget 当作它的基类:UiWidget 是"树节点"(父给尺寸、父下探脏区、
命中沿树链派发),而 PlotView(4 层位图缓存)/ SignalPanel(行位图缓存)是
"地盘宿主"。强行上树会推翻"父脏则子全脏"的前提,历史上正是这类耦合导致过
"后台渲染覆盖平移结果""命中矩形错位"的事故。头文件里已写明这条理由。
Toolbar/TabBar 是两者的交集:对外实现 UiComponent,对内持有一棵 UiWidget 树。
## 2. 宿主侧:四条手写链 -> 组件表
Application 新增 ComponentEntry{ component, target, chrome } 与 m_components
(底->顶:plot -> panel -> toolbar -> tabBar),BuildComponentTable() 装配一次。
- ApplyDpi 6 行逐个 SetScale -> 1 个循环
- Relayout 手算三列 x 坐标 -> UiStackRects(内容区, HORIZONTAL, {panelW, splitW, AUTO})
- WM_MOUSELEAVE 逐个 OnMouseLeave -> 循环累积 chromeChanged / plotChanged
- WM_SETCURSOR if/else if 链 -> 从表尾向前找首个命中者,取其 QueryCursor
顺带兑现历史缺口:UiLayout::UiStackRects 自引入以来第一次有了生产调用点。
有意保留显式的三条链(WM_LBUTTONDOWN / WM_LBUTTONUP / WM_MOUSEMOVE):它们各自带
组件专属后置动作(提交重命名、构建派生信号、面板联动滚动、更新拖拽光标、
plotOnlyMove 决定局部失效还是整帧失效)。塞进表里只能靠 lambda 字段,
可读性反而下降 —— 属于用户明确禁止的"过度抽象",因此不做。
## 3. 大文件按职责拆分(纯整函数搬移,逐像素 0 差异)
SignalPanelInteraction.cpp 2329 -> 831 行,拆出:
SignalPanelEdit.cpp 616 内联编辑(筛选框、组名/信号名、数值、颜色与透明度)
SignalPanelScroll.cpp 205 动画与滚动(折起、联动滚动、滚动到目标信号)
SignalPanelMenu.cpp 527 弹出菜单(系数/偏移、镜像、右键菜单与命令回执)
Shared/PopupMenu.cpp 1901 -> 706 行,拆出:
PopupMenuLayout.cpp 753 尺寸测量、项矩形排版、滚动可见性
PopupMenuPaint.cpp 397 背景/阴影/项绘制
Inc/PopupMenuInternal.hpp 103 pmdetail 常量 + inline 辅助(跨 TU 安全)
拆分判据是"职责"而非行数;共享常量一律收敛到 *Internal.hpp。
## 4. 代码审查(修 5 处)
- SignalPanelInteraction.cpp 头注释仍写"4 片",实际 6 片 -> 改为完整清单
- PopupMenuLayout.cpp 机械搬移时把构造函数一起搬走 -> 重复定义,只保留在交互分片
- PopupMenu.cpp 的 #include "PopupMenuInternal.hpp" 落进 namespace jdi 内 -> 移到文件头
- PopupMenuPaint.cpp 缺 UiControls.hpp(UiDrawEditFieldContent 未声明)-> 补 include
- 全仓复查历史删除符号残留引用(m_paintPlot / ClampCursorsAfterPan /
PaintFreeMarkerTooltip / CaptureDecorCache / FitSinglePointSignal / CsvTable /
DataFile / .preflight / structcheck)-> 零命中
## 5. Tools/ 剥离
git rm -r --cached Tools + .gitignore 新增 Tools/(本地文件保留,可随时复跑)。
ui_refactor 分支为全本地分支,另做 filter-branch 清除历史中的该目录。
## 6. 验证
- make release / make debug:零 error 零 warning
- rendercheck:A-H 八个场景哈希与改造前逐一相同
(A 2166933039626F66 B C444ECFEB18FD73D C 7984446D8E7A4418 D BF855879CA12F289
E 1C663E4118616016 F 0535292D8E07AE18 G 7A58172C46577729 H 23F390DD76141113)
FUNC 7/7 PASS
- uicheck:69 passed, 0 failed
- TargetCheck.exe 891,392 B(< 900 KB 阈值)
## 7. 文档
Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md
新增第五阶段章节;Documents/agent_mem.md 同步变更日志与「第七轮」硬约束。
|
||
|
|
b60debc4ee |
UI 重构 P5(1):共享表面 UiSurface + 后备缓冲改 top-down 32bpp DIB;修复退出期绘制崩溃
一、共享表面(为「整个应用共享一张后台位图」落地基础设施)
- 新增 Components/Shared/{Inc,Src}/UiSurface.*:{ dc, rect, stride, bits, version, scale }。
SubView() 把子矩形换算到宿主坐标、夹取在本表面内、按 stride 重算 bits 偏移、传递 version。
⚠️ 注释里写明:bits 只在 CPU 侧合成时使用;画控件只需 dc + rect(用 bits 画字会破坏 ClearType)。
- Application 增 BackBufferSurface();后备缓冲由 CreateCompatibleBitmap 改为
**top-down 32bpp CreateDIBSection**(biHeight 取负,y 向下递增),并记录 bits/stride/version
(重建位图时 ++version,旧句柄因此可判定作废)。
二、修复「退出时访问违例」(真实缺陷)
- Application::Shutdown() 中 **DestroyWindow 必须在 GdiplusShutdown 之前**:前一帧的 InvalidateRect
可能留下未处理更新区域,DestroyWindow 期间派发 WM_PAINT ⇒ Render() ⇒ 在已关闭的 GDI+ 上构造
Graphics ⇒ 崩溃。
- Render() 开头加保护:!m_gdiplusReady 或 m_hwnd 为空时直接返回。
验证:
- 像素:8 场景(A 全览 / B 复杂浮层 / C 平移 / D 悬停 / E 按下 / F 松开 / G 悬停主按钮 / H 移出)
哈希与 DIB 改造前**逐一相同**(A 2166933…、B C444ECF…、C 7984446…、D BF85587…、
E 1C663E4…、F 0535292…、G 7A58172…、H 23F390D…)。
- 功能:locate-cursor-button (1291,73) / click-cursor-changes-mode (1->2) /
release-outside-does-not-fire 全部 PASS。
- uicheck 69 passed / 0 failed(新增 7 条 UiSurface 断言:bits 按 stride 偏移、矩形夹取、
完全在外时返回无效表面、version/scale 传递)。
- make release / make debug 零警告;Target.exe 883,200 B(< 900KB)。
附注:探针此前的退出崩溃其实来自「探针自己又调了一次 GdiplusShutdown」(Application::Shutdown
内部已 shutdown),与本轮修复无关;本轮的顺序调整与 Render 保护仍保留为防御性正确做法。
|
||
|
|
df715a035e |
修复浮动面板事件穿透 / 自由标记总是预览最近信号 / 固定点位于GX与图例之下 / 预览曲线取值插值;体积红线放宽到900KB
1) 浮动面板不得被右键与中键穿透(用户报告:在面板上右键会弹出下面网格的右键菜单)
- 新增 PlotView::OnRButtonDown:TopPanelAtPoint 命中即置顶并消费该次右键;
Application::WM_RBUTTONDOWN 在「绘图区右键 → 网格菜单」之前调用它。
- OnMButtonDown 开头同样判定面板:命中即消费,不再进入平移。
2) 「自由标记点选取」总是预览网格内离鼠标较近的信号(用户报告:单信号时也必须贴近曲线)
- ProbeFreeMarker 删除 `if (!hitCurve) continue;`,改为两候选取更近:
① 鼠标到画出来的折线的垂直距离(阶跃竖直段只有它能命中);
② 同一时刻(鼠标时刻吸附最近采样)采样点的屏幕纵向距离。
- 门槛 tolerance 只用于「已吸附」圆环;固定点仍恒吸附到真实采样时刻。
3) 固定标记点位于 GX 编号徽标与图例之下(用户要求:数据显示不得遮挡它们)
- PaintFreeMarker 把 m_legendHits 托底矩形与 LaneBadgeRect 用 gd::Region::Exclude
排除出绘制区;仅在确有固定点/预览时才计算。
- PaintFreeMarkerCapsule 新增 vectorText:胶囊与遮挡矩形相交时文字改走 GDI+
(GDI DrawTextW 不认 GDI+ 裁剪区)。
4) 预览曲线(中心轴/均值滤波/弱峰调整)的光标取值点落回曲线
- paintDerivedCapsule 改为在光标准确时刻按时间线性插值,且先 ApplyDrawTransform 再插值
(绝对值/垂直镜像非线性,先插值会偏离折线);双光标增量同样插值。
5) 产物体积红线由 800KB 放宽到 900KB(到达阈值时提醒,不是禁止超过)。
验证(全离屏,未占用鼠标/键盘/屏幕):仓库外临时探针 25/25(A 面板右键/中键不穿透且面板外仍生效;
B 远离曲线可预览与单击固定、两信号按纵向距离择优;C 图例与 GX 区域像素差 0、整幅差异 5 万像素;
D 阶跃区标注点与曲线差 0.0px,插值点与最近采样点相差 39px/197px);A/B 恢复旧门槛 ⇒ 5 项失败,
改回最近采样取值 ⇒ 均值/中心轴各偏 36px 失败。make release / make debug 零警告;
Target.exe 860,672 字节(840.5 KiB)。
|
||
|
|
53b981a2a9 |
全项目大检查:修复数据层/UI 真 BUG、清理死代码与过期注释、并评估二进制体积
按「性能 / BUG / 错误 / 过期注释 / 命名与实际含义不符」四项对全仓做了一次审查, 四个只读子审查交叉核对后逐条回到源码确认再改。 一、数据层真 BUG - MatH5:U8/U16/U32/U64 用逐字节 Cached() 拼装,而 Cached 只服务「完全落在同一个 64KB 块内」的请求 => 跨块时静默读成 0(U32 整段为 0、U64 高 32 位为 0),分块 B-tree 的 chunk 偏移/子节点地址被破坏且无任何报错。新增 ScalarLE():命中缓存用缓存, 否则退回一次 ReadAt;U64 顺带从 8 次缓存查找降到 1 次。 - Mf4Loader::IsBlock 未校验链接表是否落在块内 => DataOffset 越界,随后 block_len - (data - offset) 在 uint64 上下溢,得到一个约 2^64 的"可用字节数", 下游 vector(usable+1) 直接 bad_alloc。同时把 offset + bytes > size 改成不产生回绕的写法。 - DecodeRawValue 的整数路径:bit_offset%8 != 0 且位宽 64 时需 9 个字节,先拼成 64 位 再整体右移会把最高字节静默丢弃 => 改为逐字节带位偏移累加。 - cg_cycle_count 加「数据段总字节 / 记录字节」上限(压缩段按 deflate 最大膨胀比估算)。 - CsvLoader 表头分支同样跳过 #/% 注释行(否则整份文件按 1 列解析并被丢弃)。 - MatLoader:double -> int 的越界转换加了范围检查;普通对象递归加深度上限与成环检测。 - Utf8Util::BuildTicks:double -> long long 越界转换前先判商是否落在 ±9e18 内。 二、界面 BUG - 工具栏按钮永远不显示悬停高亮:Paint 读的 Button::hover 全仓从未赋值,改为按 m_hoverIndex 判断。 - 菜单打开时"取消底层拖拽"对每个消息都执行,把菜单自己的 Target::EDIT_FIELD 也清掉 => 菜单内联输入框无法拖拽框选。排除 EDIT_FIELD。 - GdiplusShutdown 之前未释放 PlotView 的 GDI+ 位图(Application 是进程单例,成员析构在 WinMain 之后)=> 新增 PlotView::ReleaseGraphicsResources() 并在 Shutdown 里调用。 - WM_KILLFOCUS 现在会提交标签页/绘图区编辑并复位捕获与拖拽状态。 - WM_LBUTTONUP 不再抢走中键平移的鼠标捕获。 - TextEdit:被过滤器拒绝的字符返回 false;无选区时 Ctrl+X 只复制、不再清空整段文本。 - SignalPanel::InvalidateRows 作废编辑/待确认/改名计时等行下标状态。 - EndFilterEdit 改走 ApplyFilterText(否则输入纯空格后回车,列表仍停在筛选结果)。 - 新增 SignalBadgeVisible 作为唯一判据(原来命中只看"可见",绘制还要求"已分配网格")。 - 折起动画结束后夹取滚动量;双击分组名改名时清掉该组正在播放的折起动画。 - ScrollToSignal 复用 SignalScrollTarget(含悬浮组名避让)。 - RowNameRect 与绘制使用同一最小宽度(原来退化时返回空矩形)。 - 分隔条拖拽上限改用与 Relayout 同一公式;UpdateDragCursor 补 SPLITTER/EDIT_FIELD。 - SignalPanel 里几个守卫改为判断真正被解引用的指针;分组行 ✕ 也返回手型光标。 三、PlotView - FindStyleOnlyLanes 改用完整的 SceneStyleEntry::operator==(原来只比 4 个字段, 另一个网格同时改"调整"时带着旧像素被当成最新)。 - 宽基底图层不再烘焙拖拽光晕(LaneLayerHash 不含它),改由 PaintHoverHighlight 每帧叠加。 - PaintLaneLegend 提前返回前清掉本网格的命中矩形(否则能点中看不见的图例条目)。 - 面板主体改为完全不透明(消除平移后 4% 透出的旧波形,零额外代价)。 - PAN / Shift+滚轮改走 SetTimeView,使"自动点线"判据随平移重算。 - 内联输入框命中字体与绘制统一为 monoSmall。 - 派生曲线取值与窗口按 SourceIndexFor 映射(水平镜像时不再读到镜像另一端)。 - EdgePanelWidth 补 style/axis 空指针守卫;二分结果夹到 count。 四、清理 - 删除 Toolbar::ActionLabel、Theme::statusBg、PopupMenu 的 editResult 死机制、 Toolbar::Layout 的重复 clear()、HitRow 的恒假分支、OnDoubleClick 的重复判定、 PaintUtil 里孤立的注释块。 - 修正一批过期/错误注释(包络带"不做加宽"、LaneLayerHash 的图例说法、 均值滤波曲线的窗口说法、样式区行数与「调整」按钮顺序、FIT_ALL 退出缩放模式、 RemoveSignal 的 ✕ 位置、.mat/.dat 格式文档、MatH5 的"映射视图"、DataModel 的 "点线/被移除文件"、SignalPanel 的"行高连续缩小"、TextEdit 的 PaintUtil 引用等)。 五、二进制体积评估(按要求只评估,未做压缩修改) - release 809,472 字节;.text 652KB、.xdata+.pdata 约 75KB、.rdata 42KB、 .rsrc 20KB、.idata 11.5KB。 - 去掉 -ld3d11 -ldwmapi -ld3dcompiler(仓内无任何调用)实测 0 字节收益 (静态库只拉取被引用的对象)=> 删链接库不是压缩手段,只是配置整洁。 - 仓内已无 <iostream>/<sstream>/<filesystem>/<regex>/<locale> 等体积炸弹。 - 结论:已无明显的浪费可捡;要显著变小只能改变取舍(动态 CRT / UPX / 砍功能), 需要用户决策,本轮未动。 验证 - editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、convcheck 18/18。 - check 136/137:唯一失败项经 git stash 回到改动前复现同一数值,属该探针自身的既有 不稳定项(取决于前置用例留下的缓存/时序),与本轮改动无关。 - 真实数据:4 个 MF4 => 283/8/18/24 个信号;.mat 样例 8 信号、 带无障碍对照-防夹力曲线数据 8 信号、BIG_MAT.mat 5 信号(私有内存 +741MB)。 - make release / make debug 零警告。 文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 171-180 条硬约束 与文末本轮小节(含体积评估数据)。 |
||
|
|
24f0bf849d |
修复悬停高亮变长方形色块/联动失效 + 信号列表动画卡顿真因(行位图缓存)
一、悬停高亮恢复"整条曲线发光"(上一轮裁成鼠标附近一段 ⇒ 满幅方波上就是个长方形色块) 光晕的语义是"整条曲线一起发光",对编码器这种满幅方波,一列包络本来就跨满网格高, 裁成 144px 宽 × 满高的窗口在屏幕上就是一个像选区框的色块(用户 2026-10-18 第二轮报告)。 修法(PlotViewPaint.cpp): - PaintSignalHaloFor 恢复**整条**几何(包络带 FillPath / 折线 DrawLines); - 新增 PaintSignalHaloCached:光晕几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标无关** ⇒ 渲染一次进离屏位图,之后每帧只做一次 AlphaBlend(源位图预乘 alpha,用 256×256 查表); - 缓存键与 AcquirePolyline 同源,**不含网格屏幕位置**(滚动网格只按新位置贴同一张位图)。 210ms/帧(整条描边)→ 一次性 15ms 渲染 + 每帧 ~2ms 贴图。 halocheck 校验:悬停必命中(60/60 位置)、光晕贴合带宽(outsideBand=0)、不越出网格、 bbox 覆盖整条曲线(不是中段方块)、列表行悬停联动仍生效。 二、信号列表动画卡顿的三个真因(探针 panelstutter:129 个可见网格 + 反复"展开→切组复选框→收起") 1) 面板每帧重画 ~36 行 × ~0.35ms(十来个 GDI+ 原语)⇒ panel.Paint 24~35ms; 2) 动画帧里仍在预热网格图层:每帧重建 1 个网格图层,2500 万采样信号 10~30ms ⇒ 帧被拉长; 3) 勾选分组会把上百个**离屏**网格排进待重画队列,随后每帧补 1 个 ⇒ 数秒持续掉帧。 修法: 1) 行位图缓存(SignalPanel::PaintRowCached):按"行的可见内容"做键,整行渲染进离屏 DIB, 命中时一次 BitBlt。panel.Paint 24~35ms → 5~6ms。 ⚠️ 必须 DIB + 内存 DC(gd::Bitmap 上 GDI 会关闭 ClearType,整列文字退化); ⚠️ 行内几何在 RowRect 里平移(GDI 文本不认 GDI+ 世界变换,否则文字画到小位图之外); ⚠️ 渲染到行位图时不按列表矩形裁剪,裁剪放到贴图时的 DC(GDI 不认 GDI+ 裁剪, 必须自己 SaveDC + IntersectClipRect,折起动画时行不许画到组名上); ⚠️ 缓存上限 192 行(≈10MB)。静态画面逐像素一致(面板区域 54/275200 不同)。 2) 新增 PlotView::SetAnimationBusy:UI 动画期间不预热网格图层(队列保留,动画结束后继续)。 3) 待重画队列只保留可视 + 预取带内的网格;RollWideBuffer 不再登记离屏网格。 实测:展开 p50 35ms → 13ms、收起 33ms → 13ms、动画帧内图层重建 56 次 → 0 次。 三、试过并放弃(避免以后重走) - ExtTextOutW 替代 DrawTextW:快 2~4 倍但光栅化不同(面板 10% 像素差异)⇒ 观感变化,放弃; - 用 gd::Bitmap 做行位图:文字丢失 ClearType,放弃。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/panelstutter_main.cpp(8/8)、halocheck_main.cpp(16/16); - 回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、gridanim 93/93、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、panflash 19/19、stress 6/6; - make release / make debug 零警告(TARGET_FILE_NAME=TargetCheck/TargetCheckD)。 文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:高亮语义不得为性能让步、 GDI 文本的三个隐形陷阱、面板卡顿先量"每行成本×可见行数"与"本帧是否顺带做绘图区重活"、 动画帧内绝不做可延迟的整帧级工作)。 |
||
|
|
6b15575829 |
滚动动画根治(巨型信号不再影响滚动)+「N 项」计数点击语义 + 浮动面板首现右上角 + 开关/关闭面板卡顿
一、滚动过渡动画在巨型信号处卡顿 —— 真因是「退化成整幅重绘」 滚动到**从未构建过图层的区域**时,可见网格一个都贴不出图层像素 ⇒ BlitWideBuffer() 返回 false ⇒ 落到 Paint() 末尾的整幅重绘 ⇒ 把本帧全部可见网格的波形(含千万级采样的巨型信号)每帧重画一遍。 实测(MF4 124 网格 + VERY_BIG_DATA 5 网格,点选最后一个信号):单帧 20~35ms,且「可见的巨型信号 网格数」越多越慢(0 个 ≈20ms、5 个 ≈34ms);对照手动拖滚动条只要 ~6ms。 修法:BlitWideBuffer 只要还能画网格底衬(backing 非空)就返回 true, 只有 blits 与 backing 都为空时才交给整幅重绘 —— 这正是「没来得及画信号时就按只有网格叠底的状态继续滚动」。 实测:动画均值 23.0→11.2ms、最坏 35.6→13.1ms;含 5 个巨型信号网格的帧 33→10.7~12.3ms。 二、面板动画帧只重绘面板(额外 −2.7ms/帧) 新增 Application::m_panelDirty:滚动定时器在只有面板动画在跑时置位,Render 走 else if (panelOnly) 只重绘面板 + 分隔条(SignalPanel::Paint 会铺满自己的区域)。m_chromeDirty(真内容变化)仍走完整装饰重绘。 ⚠️ 回送仍是 if (fullFrame) {整窗} else {绘图区} 的无条件分支(遵守 §9.41)。 实测帧耗时拆解:plot 4.8ms / plot+panel 11.4ms / plot+全装饰 13.9ms。 三、分组行「N 项」计数:只折叠展开,绝不进入改名 新增 RowPart::GROUP_COUNT 与 SignalPanel::GroupStatsRect(row)(绘制与命中的唯一几何来源,命中矩形在 文本左右各外扩 3 逻辑像素)。RowHitPart 在名称区域之前判定计数区: - OnLButtonDown:折叠/展开;OnDoubleClick:只折叠/展开,任何点击频率都不进入改名; - DoubleClickIsPlainPress 返回 true ⇒ 快速连点每一击都生效。 ⚠️「快速双击进改名」的计时只对 RowPart::BODY 记录(否则“点计数、紧接着点名称”会被误判成双击)。 四、浮动面板首次出现的位置:右上角 旧默认位置 top = plotTop + 16 + laneIndex*28:上百个网格时 laneIndex 很大 ⇒ 被夹到右下角 (实测 plotH=1081 时 topMargin=964)。改为按「当前真正显示出来的面板中排在本面板之前的个数」错开 (固定遍历顺序:网格下标 → 面板种类),并把错开量夹到“面板下沿不超过绘图区 45% 高度”以内: 第一个面板错开 0 ⇒ 正好贴在上右角(实测 topMargin=24、rightMargin=24),6 个面板时最坏 topMargin=234。 ⚠️ 判定「面板是否显示」不能只看 *Panel.open —— LanePanelState::open 默认就是 true,必须同时看功能启用。 五、开关实验性功能 / 关闭面板卡顿 —— RebuildWideBuffer() 整批重建 四个 SetLane* 末尾都调 RebuildWideBuffer()(注释说是“预建宽缓冲基底”),而它同步重渲染全部网格的图层: 129 个网格(含 5 个千万级采样的巨型信号)整批重建要几百毫秒 ⇒ 点一下功能开关 / 关闭浮动面板就会卡住。 修法:新增 PlotView::EnsureLaneLayersPrefetched() —— 只有「图层数组与网格数量不符」(首次绘制 / 增删网格) 才同步整批建立;其余只 RollWideBuffer() 登记缺失/过期网格,由既有预算(每帧最多一个)逐帧补齐。 实测:几百毫秒 → ≤22ms;巨型信号网格上开关均值滤波同样 15ms 量级。 验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/lanescroll_main.cpp(lanescroll.exe)23/23:逐帧耗时 + 「可见巨型信号网格数」关联、 「N 项」计数热区与双击不进改名、面板首现位置与多面板错开、开着巨型信号的开关/关闭面板耗时。 - 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、 gridanim 93/93。 - make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。 文档:Documents/agent_mem.md 新增 §9.44、§11.2 第 134–140 条与变更日志一行。 |
||
|
|
436dcbfd52 |
滚动过渡动画根治 + 快速连点跟手 + 分组名单击展开 + 光标吸附同源 + 按钮淡出对齐 + 滚动位置复位
一、滚动过渡动画卡顿(用户报告:甚至不如手动拖滚动条)—— 三处独立根因
1) WM_TIMER(kScrollTimerId) 无条件整窗失效 ⇒ WM_PAINT 看到更新区域不在绘图区内 ⇒ 把
m_chromeDirty 置真 ⇒ 每一帧都重绘标签栏/工具栏/面板。
修法:用「推进之前」的面板动画状态决定失效范围 —— 面板动画(滚动到目标信号 / 分组折起展开)
确实要改面板像素 ⇒ 整窗失效;只有绘图区动画(网格滚动到可视区 / 回到最上方)⇒ 只失效绘图区。
实测:绘图区动画帧 13.6ms → 3.5ms;手动拖滚动条参照 4.2ms。
⚠️ 未改变 §9.41 的回送结构(仍是 if (fullFrame) {整窗} else {绘图区})。
2) 图层预热不足:上百个离屏网格长期不建图层,长距离滚动动画扫进去时每一帧都有十几个网格
没有图层像素,只能靠「网格底衬兜底」重画(约 0.5ms/网格 ⇒ +8ms/帧)。
修法:LaneVisuallyRelevant(laneIndex, prefetchScreens) 增加 ±1 屏预取带(AnyVisibleLanePending 用),
并新增 PrefetchStepAllowed()(kPrefetchMinIntervalMs=32 ⇒ 约 31 次/秒)限速地继续请求重绘,
让整批图层在静置期间逐步建好。
实测:lane-only + VERY_BIG_DATA 帧 13.9ms → 5.3ms;signal-click + VERY_BIG_DATA 18.3ms → 10.3ms。
3) 网格滚动量原本参与「宽基底可用性」判据(上一轮已修:SceneContentHash(..., includeLaneScroll=false))。
二、光标吸附改用「放大自动点线」的同一判定(用户 2026-10-17 修订)
AutoPointLineNeeded 改名 MinorCellSparse(中性名,同一判定两处用途):任一小格内有效数据点 < 5 即为稀疏。
- 「放大自动切点线」与「光标吸附」共用它;判定完全不看绘制方式 ⇒ 即使用户手动改过显示样式也照样吸附。
- 鼠标下方网格内既有符合又有不符合的信号时,只有符合的那条的数据点参与吸附;没有符合的则自由移动。
- 删除旧的整屏判据 SignalSnapEnabled 与 kSnapVisibleCellsPerPoint(死代码)。
三、「回到最上方」按钮淡出与「浮动提示」一致
kBackToTopFadeOutMs = kHintFadeMs(500ms)+ 线性 alpha,到达顶部立即开始淡出(不停留)。
淡入保留 170ms ease-out。实测 50% 时刻 alpha = 0.500(线性),旧 ease-out 会是 0.875。
四、快速连点跟手(根因:CS_DBLCLKS 合成 WM_LBUTTONDBLCLK 导致按下丢失)
组件各自实现 DoubleClickIsPlainPress(x, y)(该落点有没有双击语义),宿主 WM_LBUTTONDBLCLK 据此
选择走 OnLButtonDown(当作按下)还是 OnDoubleClick:
- PlotView:浮动面板(含复选框/滑块/复位/✕/标题栏)、「回到最上方」按钮、绘图区滚动条、GX 徽标 ⇒ 按下;
时间光标线/读数标签/图例条目名/绘图区空白 ⇒ 保留双击。
- SignalPanel:样式区数值框、「目标」行名称与 GX 标签、行名称区域 ⇒ 保留双击;其余全部 ⇒ 按下。
- 弹出菜单 / 拾色器打开时,WM_LBUTTONDBLCLK 也按各自 OnLButtonDown 走。
五、分组行:名称恢复单击展开收起 + 只有快速双击进入改名
- OnLButtonDown 分组分支不再排除 RowPart::BODY:三角 /「组X」/ 名称本体统一折叠展开。
- OnDoubleClick 名称区域:只有快速双击(kGroupRenameDoubleClickMs=300,比系统 500ms 紧)才进入改名;
超阈值的「慢双击」只当又一次单击;快速双击前先撤销按下那一次的折叠切换,保证进编辑时状态与点击前一致。
六、信号重现时网格滚动回到最上方
“有过需滚动条的网格 → 没有任何信号显示 → 又出现需滚动条数量的网格”这一类情形必须从最上方开始看。
修法:Application::SynchronizeView 的「首次出现可见信号」分支里,在 FitAll() 之后 tab.LaneScroll() = 0。
验证(全部离屏,不占用鼠标/键盘/屏幕)
- grabdanim(.preflight/perf/gridanim_main.cpp)93/93:真实消息循环帧距 + 逐帧渲染耗时(与手动拖滚动条
对比)、光标吸附新判定、按钮淡出线性、快速连点路由、分组名单击/双击/慢双击、滚动位置复位。
- 既有回归:verify 49/49、panelanim 158/158(按新分组名规则更新 8 条断言)、datacheck 69/69、
edgecheck 46/46、errcheck 19/19。
- make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与正在运行的实例冲突)。
文档:Documents/agent_mem.md 新增 §9.43、§11.2 第 125–133 条与变更日志一行。
|
||
|
|
f845616938 |
回退 5b52eef 并修复「屏幕不刷新」根因:拖动滚动条卡顿 / 悬停高亮卡住 / 信号拖拽无效果
根因(代码级确认)
- Render() 把界面画进后备缓冲区后用 GetDC + BitBlt 回送窗口,5b52eef 把回送写成
`if (fullFrame) { BitBlt(整窗) } else if (panelOnly) { BitBlt(本帧重绘区域并集) }`,没有兜底 else。
- 「鼠标只在绘图区内移动」的帧(WM_MOUSEMOVE 的 plotOnlyMove 路径只 InvalidatePlotRect)恰好
fullFrame == false && panelOnly == false ⇒ 后备缓冲区被正确重绘,窗口 DC 一个像素都不回送。
- 这一个根因同时造成用户报告的三个症状:悬停光晕卡住(移开不消失)、拖动滚动条不跟手/非常卡顿、
拖拽信号换网格看不到任何结果。
修法
- git reset --hard HEAD~1 撤销 5b52eef(保留 backup-bad-commit 标签供 A/B)。
- 只挑回该提交里与更新区域无关的功能改动(用户明确要求过、本身正确):
* SignalPanel::ExpandGroupForSignal():联动滚动前立刻展开折起的分组(不播展开动画);
* FitAll() / FitSinglePointCluster() 不再复位 Tab().LaneScroll()(适应视图不影响滚动条);
* 右下角「回到最上方」浮动按钮 + 网格滚动到可视区的平滑动画。
- Application.cpp 只保留 3 处最小改动:Render 里把 panelAnimating 计入整帧脏、WM_TIMER 推进
AdvanceBackToTopAnimation() 且保持整窗失效、定时器存活判定带 BackToTopAnimationActive()。
Render 的绘制与 BitBlt 路径逐字节等同于 82b5ebe。
- 彻底删除 m_paintPlot / m_panelDirty / panelOnly / InvalidatePanelRect / 并集回送。
验证(全部离屏执行,不移动真实光标、不抢焦点、不截屏)
- .preflight/diag(新增离屏探针,19/19):窗口移到屏幕外 + WS_EX_TOOLWINDOW + SW_SHOWNOACTIVATE,
保证有真实更新区域与 WM_PAINT 但不影响用户。断言滚动条拖动(滑块与绘图区都在后备缓冲区被重绘)、
悬停光晕出现与消退(像素完全回到基线)、在曲线上按下进入 MOVE_SIGNAL 且拖到另一网格后归属正确。
- .preflight/check 137/137。
- .preflight/diag/verify_blit.ps1(新增源码级护栏,10/10):断言 Render() 的回送块必须有无条件 else、
不得出现 else if、m_paintPlot/m_panelDirty/panelOnly 必须不存在、失效只允许整窗或仅绘图区。
A/B:换回 backup-bad-commit ⇒ 4 项如实失败;换回修复版 ⇒ 10/10。
- make release 零错误零警告。
文档:Documents/agent_mem.md 新增 §9.41、§11.2 第 108–116 条(含 §9.40 与旧 108–114 条的作废标注)
与变更日志一行。
教训(写进记忆):① 离屏探针无法验证「窗口回送 / 合成」(屏幕外 DWM 不合成、窗口 DC 恒为空白)⇒
回送类约束必须有源码级护栏;② 离屏窗口 + TrackMouseEvent 会立刻收到 WM_MOUSELEAVE,探针须丢弃该消息;
③ 网格回收会让下标左移,断言要用锚信号。
|
||
|
|
82b5ebe4f9 |
「所属网格」菜单:标题固定 + 10 行上限 + 滚动条;网格滚动联动改为动态过渡
1) 菜单滚动时最上方的「所属网格」标题不再跟着滚走
- 根因:上一轮只实现了“尾部固定区”(最后一个分隔线 + 「新建网格」钉在底部),
而滚动区是从条目 0 开始的,于是标题也被一起滚掉。
- 修法:BuildLayout 新增 m_scrollFirst(= 前导 HEADER / HEADER_ACTION 的个数),
滚动区收敛为半开区间 [m_scrollFirst, m_pinnedIndex):
· 前导标题从菜单顶向下固定排(不参与滚动);
· 滚动区条目受 m_scrollOffset 影响;
· 尾部固定区从菜单底向上对齐。
- 配套:Paint 的裁剪与“跳过不可见条目”范围、ItemAtPoint 的“必须落在滚动视口内”判定、
EnsureItemVisible 的下界全部改用该区间;新增 ScrollRegion(first, last) 供查询/测试。
2) 菜单长度上限改为「最多 10 条网格」并加滚动条
- MenuLayoutConstraint 新增 maxScrollRows(滚动区最多显示的标准行数;与 maxHeight
同时给出时取更严格者);OpenLaneMenu 传 10(kLaneMenuVisibleRows),
另保留 maxHeight = max(180, 客户区高 × 0.8) 作为矮窗口的硬上限。
- 新增滚动条:轨道贴滚动条目区右内侧(不盖住标题与底部固定项),滑块高度按视口/内容比
(下限 22 逻辑像素);滚动区条目宽度让出 kScrollBarWidth + kScrollBarGap,
文本与对勾不会压在滑块上。
- 滚动条可拖拽:按下滑块 → 拖动(只改偏移、不更新悬停);点击轨道空白 → 滑块中心移过去;
松开结束拖拽且不下发命令、菜单保持打开。新增 ScrollOffsetForThumbTop 反算偏移。
- ⚠️ BuildLayout 绝不能复位 m_scrollBarDragging:拖拽时每帧都会 RebuildLayout(),
在那里清零会让拖拽“拖一下就脱手”(该状态只在 Open / Close 里复位)。
3) 网格滚动联动改为动态过渡(旧实现直接把视界瞬移过去,过于僵硬)
- PlotView::ScrollLaneIntoView 只负责启动平滑动画:与信号列表的“滚动到目标信号”同源
(ease-out cubic;时长 clamp(距离×0.35ms, 130, 300)),新增
m_laneScrollAnimActive/From/To/StartMs/DurationMs。
- 宿主 16ms 定时器(kScrollTimerId)调用 AdvanceLaneScrollAnimation() 逐帧推进,
与信号面板动画共用同一条定时器;Render 的“需要保持定时重绘”判定加入
LaneScrollAnimationActive()。
- 每帧 ClampLaneScroll() + BuildLaneVisuals()(网格滚动量参与场景/图层指纹与各网格 rect);
结束时精确落到目标。已完整可见的网格仍然不启动动画。
- ⚠️ 用户自己滚动网格(滚轮 / 拖滚动条)会 CancelLaneScrollAnimation(),避免与动画抢滚动位置。
验证(全部离屏执行:窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- .preflight/check 扩到 95 项,95/95 通过;新增断言:
· 菜单顶部标题在滚动时不动(Y 坐标不变)、滚动视口高度恰为 10 × 行高;
· 滚动条轨道/滑块存在、随滚动移动、按住滑块拖到底(菜单不关、不下发命令、末条目入视口)、
滚轮回到顶部;
· ScrollLaneIntoView 不瞬移(刚触发时几乎未动)、中间帧单调递进、收尾精确到位、
已可见时不启动动画、滚轮/手动滚动会取消动画。
- A/B:回退三处(标题固定 / 10 行上限 / 滚动动画)⇒ 10 项如实失败;恢复后 95/95。
- make release / make debug 零警告。
文档:Documents/agent_mem.md 新增 §9.39、§11.2 第 104–107 条与变更日志一行。
|
||
|
|
2904699eae |
修复五项交互缺陷:折起后滚动条 / 单点信号样式 / 线宽实时生效 / 网格菜单限高滚动 / 点选信号滚动绘图区
1) 分组折起后再展开,信号列表的滚动条消失(筛选态与常规态均可复现)
- 根因:AdvanceGroupAnimations 只在“折起完成”时重建行集合;而“展开完成”那一帧
m_contentHeight 从收起态跳回展开态,却没有任何路径重算滚动条可见性 ⇒ 滚动条停在隐藏。
- 修法:任一方向动画结束都 m_rowsDirty = true + BuildRows(),并每帧 RefreshScrollBar(false)。
⚠️ 动画期间不夹取滚动偏移,避免打断与滚动同步的折起动画(scrollSynced)。
2) 单点信号(finiteCount == 1)默认绘制方式由「点线」改为「散点」
- ApplySinglePointDefaultStyle → DrawMode::SCATTER(仍取最大线宽)。
- 新增 SignalPanel::ModeOptionEnabled:单点信号的「线段」「点线」置灰、不可点、
且不参与悬停高亮(避免“看着能点其实点不动”);样式区的绘制与命中都用它。
- PlotView::UpdateAutoScatterStyles 跳过单点信号,否则“放大自动点线”会把它又改回「点线」。
3) 拖动线宽滑块完全不生效(拖动时线宽不变、松手瞬间变了、再移动鼠标又变回去)
- 根因一:AcquirePolyline 的折线缓存键没有 lineWidth,而 BuildPolyline 把线宽烘进
包络带几何(每个像素列的上下包络各向外扩 max(1,线宽)*scale*0.5,「梳状」判定也依赖它)
⇒ 稠密信号(采样数 > 绘图区宽度)永远复用旧线宽的顶点与包络带。
- 根因二:样式补丁只更新“静态场景缓存 + 后备缓冲区”,而每网格图层(宽基底)仍是旧样式像素,
下一帧命中“宽基底快路径”时把新样式覆盖回旧像素。
- 修法:PolylineCacheEntry 增加 lineWidth 并进比较;
样式补丁之后 QueueLaneRebuild(受影响网格) + RenderPendingLaneRebuilds(1)(沿用每帧至多 1 个的预算)。
4) 「目标」行的 GX 标签复用「所属网格」菜单,并先给 PopupMenu 增加「限高 + 条目区滚动 + 尾部固定项」
- MenuLayoutConstraint::maxHeight(OpenLaneMenu 传客户区高度的 62%);
- 固定区 = 最后一个分隔线及其之后的条目(即「新建网格」)始终钉在菜单底部,其余条目可滚动;
- 滚轮滚动条目区(Application 在菜单内滚动时不再关闭菜单,其余情形保持既有行为);
- 键盘移动悬停项自动滚入可视区(EnsureItemVisible);ItemAtPoint 要求条目同时命中滚动视口;
- 滚动区文本用 UiTextClipped(GDI 文本不遵循 GDI+ SetClip),并跳过完全不可见的条目(性能);
- BuildLayout 不再无条件清零 m_scrollOffset(否则 RebuildLayout 会把滚轮位置吃掉);
- 「目标」行 GX 标签点击**不参与**联动滚动(悬停联动保留)。
5) 点选信号名 ⇒ 绘图区自动把目标网格滚动到可视区内(上百个独立网格时旧有高亮无法定位)
- SignalPanel::RequestPlotLaneScroll / TakePlotLaneScrollRequest;
Application::ApplyPendingPlotLaneScroll(WM_LBUTTONDOWN 的面板分支);
PlotView::ScrollLaneIntoView(在可视区上方就上滚、在下方就下滚、已完整可见则不动)。
- 只有“点击信号名”触发;悬停绝不改变视图;勾选框 / GX 徽标 / 移除 ✕ 也不触发。
验证(全部离屏执行,窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- 新增 .preflight/check(不入库)68/68 通过;
- A/B:五处修复全部回退 ⇒ 17 项如实失败(B 4 / C 4 / D 7 / E 2);“折起后滚动条”需要
逐帧重绘驱动动画才能复现,单独回退时 ⇒ 5 项如实失败;恢复后 68/68;
- make release / make debug 零警告。
其它:
- 按要求删除工作区 Tools/(structcheck 脚本)并移除 Makefile 的 structcheck 目标;
- Documents/agent_mem.md 同步:新增 §9.38、§11.2 第 97–103 条与变更日志一行。
|
||
|
|
d2e6fdd085 |
收尾:LanePanelState 抽取 + 错误提示非模态化(清空上一轮的“未完成”)
一、Lane 的内聚性(上一轮建议 5,本轮完成)
- 新增 struct LanePanelState { bool open = true; double x = -1.0; double y = -1.0; };
- Lane 的 12 个平铺面板字段(edgePanelOpen/X/Y 等 × 4 类实验性功能)
→ 4 个聚合成员 edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel;
- 访问点全部改为 lane.edgePanel.open 形式(DataModelTab.cpp 快照拷贝、
PlotViewInteraction.cpp 面板拖拽、PlotViewPaint.cpp 面板层序绘制);
- RebuildLanes 内部的 LaneCarry 同步收敛为 4 个 LanePanelState,
12 行逐字段赋值简化为 4 行整体赋值。
- 教训:批量改名不能对“声明”和“使用”跑同一组替换对,
否则声明行会变成 `bool edgePanel.open = true;` 这类非法代码。
二、错误提示与模态阻塞解耦(上一轮建议 1,本轮完成)
- Application 新增 SetModalErrorsEnabled(bool) / ModalErrorsEnabled(),
成员 m_modalErrorsEnabled 默认 true(交互场景行为不变);
- Initialize 最先执行 SetModalErrorsEnabled(showCommand != SW_HIDE):
隐藏窗口(自动化/离屏)下没人能点掉模态框,等于把进程钉死;
- SetStatusMessage(text, isError=true):允许模态时原样 MessageBoxW,
否则改走非模态通道 m_plot.ShowPersistentHint(text)(绘图区底部浮层);
普通提示与空文本依旧被忽略;
- PlotView 新增公开接口 ShowPersistentHint(text)(内部转 SetHintSticky),
不向宿主暴露粘滞/淡出等内部细节。
三、顺带修掉一个真实缺陷
- PlotView::Paint 在 m_lanes.empty() 时提前 return,而提示浮层在其后的
叠加层绘制 ⇒ 一个信号都没勾选时任何错误都显示不出来,恰恰是
“文件加载失败”最需要提示的场景;该分支补一次 PaintHint(g)。
四、验证(全部离屏:SW_HIDE + 不注入输入 + BELOW_NORMAL)
- make release / make debug:通过,零警告;Build/Release/Target.exe = 764416 B;
- verify 49/49、datacheck 69/69、edgecheck 46/46、panelanim 156/156;
- 新增探针 .preflight/perf/errcheck 19/19:默认值 / 隐藏窗口自动降级 / 宿主显式开关;
工作线程 + 5s 超时判定“不阻塞”(旧行为会永久阻塞,判失败并 ExitProcess,
探针不会挂住);EnumWindows 统计本进程 #32770 顶层窗口数量前后不变;
错误文本进入 m_hintText / m_hintSticky / HintNeedsAnimation / m_hintShownAt;
像素级:有提示帧与无提示帧在绘图区内的差异像素 3781,且全帧差异同为 3781
(证明差异只来自提示)。
五、文档
- Documents/agent_mem.md:§9.37 新增第八节(本轮补齐)、第九节(仅剩 MF4 项)、
§11.2 新增第 93–95 条硬约束、变更日志新增一行;
- Documents/refactor_report_2026-10-16.md:§7.3 / §7.4 标记完成,新增 §九 收尾补记
(含 9.1–9.5:聚合化、解耦、探针、验证结果、变更清单)。
|
||
|
|
e6b7bf66ec |
按组件目录结构把四个巨型实现文件拆分为多个协作文件(PlotView / SignalPanel / Application / DataModel)
承接上一条提交(0871eb0)—— 该提交只完成了 Components/Shared 与注释/命名清理,并**主动未做**
大文件拆分;本提交把大文件拆分补齐,至此“按功能拆分逻辑块到各组件自己的 Inc/Src”已落地。
## 一、拆分结果
| 原文件 | 拆分后 |
| --- | --- |
| Components/PlotView/Src/PlotView.cpp(13 695 行 / 544 KB) | PlotView.cpp(787) + PlotViewPaint.cpp(6 367) + PlotViewInteraction.cpp(2 715) + PlotViewFeatures.cpp(3 005) + PlotViewInternal.hpp(936) |
| Components/SignalPanel/Src/SignalPanel.cpp(4 184 行) | SignalPanel.cpp(1 141) + SignalPanelPaint.cpp(697) + SignalPanelInteraction.cpp(2 095) + SignalPanelInternal.hpp |
| Components/Application/Src/Application.cpp(3 135 行) | Application.cpp(1 427) + ApplicationCommands.cpp(1 184) + ApplicationLoading.cpp(270) + ApplicationInternal.hpp |
| Utilities/Src/DataModel.cpp(2 615 行) | DataModel.cpp(1 583,含自由函数区) + DataModelTab.cpp(732) + DataModelInternal.hpp |
分片职责(以 PlotView 为例):
core 视图核心:构造/析构、上下文、布局、坐标映射、视界与平移、网格结构与归属
paint 渲染与像素缓存:全部绘制、场景/装饰缓存、每网格图层、宽基底与滚动补条
interaction 交互与编辑:命中测试、鼠标/键盘、光标形状、内联输入框、浮动面板层序
features 视图特性与派生数据:光标读数/提示/缩放、自适应、吸附、单点、派生曲线线程池、
边沿统计、实验性功能参数
(SignalPanel 为 core/paint/interaction;Application 为 core/commands/loading;
DataModel 为 core/tab。)
## 二、关键手法(这是能安全拆开的前提)
原文件的辅助区都是**匿名命名空间**(PlotView 约 870 行常量与工具函数),被所有成员函数广泛引用。
直接拆文件会让各 .cpp 拿不到这些工具,因此:
1. 把该块整块搬进 `<组件>Internal.hpp`,从 `namespace { }` 改为**具名**命名空间
(pvdetail / spdetail / appdetail / dmdetail);
2. 块内**每个函数与常量都标 inline** ⇒ 多个翻译单元共用同一个定义:既不重复符号,
也不产生 N 份函数定义;
3. 头部末尾 `using namespace <ns>;` ⇒ 各 .cpp 函数体里原有的非限定调用(IntersectRect /
NiceStep / ClampDouble …)**一行都不用改**;
4. 成员定义按「方法名 → 分片」归类后**整块搬移** —— 全程**纯代码搬移**,没有改动任何一行函数体。
## 三、拆分过程中踩到并修正的四个坑(已固化到 .preflight/perf/split_component.ps1)
1. `[[maybe_unused]]` 等属性**必须排在 inline 之前**(`inline [[x]] T` 非法);
单独的 `{`/`}` 行、注释行、`struct`/`enum`/`using`/`template` 行不能加 inline。
2. 匿名命名空间的**结束行可能带注释**(`} // namespace`),只认 `^\t}$` 会一路匹配到后面
某个成员函数的结束括号,把大段成员定义误当成“辅助区”搬走。
3. **首个成员定义之前的所有行**必须跟着进核心文件:DataModel.cpp 在匿名命名空间之后、
首个成员定义之前还有约 390 行命名空间外的自由函数(SignalStats / ComputeStats /
CacheSparseSamples / ApplySinglePointDefaultStyle …),漏掉会被静默丢弃。
4. 内部头必须带上原文件真正用到的全部头:DataModel 需要 Theme.hpp / <cstring> / <cstdio> /
<deque> / <atomic> / <cwctype> / <new>(原文件靠这些头才编译得过)。
## 四、附带完成的命名审计
PlotView 中残留的 File 语义命名一并修正:LaneSpansMultipleFiles → LaneSpansMultipleGroups、
firstFile → firstGroup、multiFile → multiGroup、SceneCacheKey::haloFile → haloGroup。
## 五、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL)
- .preflight/perf/verify.exe(PlotView 渲染/交互,含逐像素断言)49/49
- .preflight/perf/panelanim.exe(信号面板:卷尺式折起/展开、筛选态、悬浮组名、目标行)156/156
- .preflight/perf/datacheck.exe(数据层:单轴/多时间轴 CSV、MF4、重命名语义)69/69
- .preflight/perf/edgecheck.exe(边沿统计与面板排版)46/46
- make release / make debug 零警告。
即:拆分后**行为与拆分前完全一致**(纯代码搬移,四套探针全绿)。
## 六、体积(诚实记录)
Target.exe: 754 176 → 764 416 B(+10 240 B,+1.4%)
因为 inline 工具函数会在每个引用它的翻译单元里各生成一份(PE/COFF 链接器没有 ICF 合并)。
这再次印证:拆分解决的是**可维护性**,不是体积(本轮的 −24.6% 全部来自 ICO 256×256 条目改 PNG)。
## 七、文档
Documents/agent_mem.md:§9.37 新增“七、多文件拆分(已完成)”与“八、仍未完成”(只剩
Lane → LanePanelState、模态提示解耦、MF4 压缩块三项);§11.2 新增第 91、92 条(多文件化的
唯一安全手法 + 体积代价)。Documents/refactor_report_2026-10-16.md 同步更新 §五/§七/§八。
|
||
|
|
0871eb0cba |
分组折起热区与改名输入框修正 + 数据模型去冗余(单一名称)+ 新增 Components/Shared 组件 + 过时注释/命名大清理 + 二进制瘦身 25.6%
## 一、UI 交互修复(用户报告) ① 分组折起热区(用户要求):点分组**名称**不再折起,只有**折叠三角**或**「组X」标签** 才折起。RowPart 新增 GROUP_TITLE;RowHitPart() 为分组行判出三角 / 复选框 / 关闭 ✕ / 「组X」标签矩形 / 其余 BODY;OnLButtonDown 只在这两个热区调用 ToggleGroupFromRow()。 附带:双击「组X」不再进入改名(该区域现为折起热区),双击分组名仍可改名。 ② 分组改名输入框不再吞掉「N 项」:RowNameRect() 对分组行的右边界改为「项数左边缘 − 6*scale」; 新增 GroupLabelText()/GroupStatsText() 作为文本与宽度的唯一来源(PaintGroupRow 与 RowNameRect 共用),避免名称可用宽度与实际绘制文本漂移。 ## 二、数据模型去冗余(用户指出的重点) - SignalData / SignalGroup **只保留一个名称字段** name:删除 displayName、删除 Title() (31 处调用点改为直接用 name);DataSet::SetSignalDisplayName / SetGroupDisplayName → RenameSignal / RenameGroup,且**空串 / 纯空白一律拒绝**(名称不再有回退值)。 依据:信号靠 SignalRef 下标定位、从不按名字匹配 ⇒ 两个字段是同一语义的两份副本。 收益:每条信号 / 每个分组少一个 std::wstring(24B + 堆分配)与一条 Title() 分支。 - 删除 Application::m_loadingBytes(全仓既不读也不写的死成员,注释却称“用于展示体积”)。 ## 三、新增 Components/Shared 组件(通用 UI 控件) - 从 Utilities/PaintUtil.* 迁出全部「控件类」绘制/命中:图标(+ / ✕ / 三角)、复选框、 滑块(UiSliderRatioFromX / UiDrawSlider)、内联输入框(UiDrawEditField / UiDrawEditFieldContent / UiEditTextLeft / UiEditOffsetAt / UiEditFieldMouseDown / UiEditFieldMouseMove)、颜色样块(UiDrawSwatch / UiSwatchNeedsOutline)、 取值胶囊配色(UiInk / UiColorLuminance / UiContrastInk)。 - PaintUtil.* 只保留低层绘制原语(矩形 / 圆角 / 直线 / 批量实线 / 颜色混合 / 文本与测量)。 - 依赖方向固定:Components/* → Components/Shared → Utilities(不得反向,Utilities 不再含控件)。 - Makefile 与 .preflight/perf/build.ps1 已登记新目录;调用方改 include "UiControls.hpp"。 ## 四、过时注释 / 命名批量修正(承接上一轮未做完的部分) - 注释:文件下标→分组下标、文件内信号下标→组内信号下标、所属文件下标→所属分组下标; 措辞统一 文件分组→分组、移除该文件→移除该分组、打开 CSV 文件→打开数据文件 等一批。 - 文不对题的形参名:VisibleIndexRange(const TimeAxis& file,…) → axis; Application::LoadFileAsync(…, SignalGroup& file,…) → group。 - 函数名里的 File 语义(上一轮只改了 RowKind::FILE):PaintFileRow→PaintGroupRow、 FileCloseRect→GroupCloseRect、OpenFileMenu→OpenGroupMenu、FileGroupLabelWidth→GroupLabelWidth、 StyleEditField::FILE_NAME→GROUP_TITLE;局部变量 fileRow/fileHeight/isFile → groupRow/groupHeight/isGroup。 - 修复 DataModel.hpp 两处被压扁的注释块(/**<TAB><TAB>* @brief …)与 Application::NewTab 上方重复的文档块。 - ⚠️ 踩坑:GROUP_NAME 撞上 nb30.h(windows.h 引入)的 #define GROUP_NAME 0x80, 导致枚举编译失败,改名 GROUP_TITLE 解决。 ## 五、二进制瘦身:1 013 248 → 754 176 B(−25.6%,省 259 KB) - 真因不是 PlotView.cpp,而是 Resources/JustDataInspector.ico 里 **256×256@32bpp 以未压缩 BMP 存储的条目(270 376 B,占整包 27%)**。改为 PNG 存储(Vista+ 标准做法,观感零变化) ⇒ 该条目 270 376 → 11 280 B、整个 ico 302 430 → 43 334 B(小尺寸仍保留 BMP)。 - 实测结论:“拆分大文件 ⇒ 瘦身”不成立(同一批机器码仍要进二进制); -ffunction-sections/-fdata-sections/-Wl,--gc-sections 在本工程反而 +9 216 B(已回退)。 - ⚠️ .o 体积受节对齐/重定位表主导,判断“代码搬去哪了”要用 nm,不要看 .o 字节数。 ## 六、验证(全部离屏:SW_HIDE + 不注入真实输入 + 进程/线程 BELOW_NORMAL) - panelanim 156/156(连跑 5 次稳定;新增/更新:点分组名不折起也不滚动、扫掠定位「组X」热区、 筛选态与普通态折叠三角逐像素一致、改名输入框右边界早于项数文本);verify 49/49; datacheck 69/69(含“重命名写入唯一名称字段且空名被拒”);edgecheck 46/46。 - A/B:回退两处 UI 修复 ⇒ 10 项如实失败;恢复后全绿。 - make release / make debug 零警告。 ## 七、本轮未完成(已在报告与 agent_mem.md 中如实记录) - PlotView.cpp(13 695 行 / 544 KB)按功能拆分为多个协作文件:未执行。 前置改造是把第 13–888 行的匿名命名空间(~870 行常量与工具函数)改成具名命名空间并逐个 inline,再逐个文件搬移并反复验证;时间不足以安全完成,故主动不做(附可执行方案)。 - SignalPanel.cpp / Application.cpp / DataModel.cpp 的多文件化、Lane 抽出 LanePanelState、 SetStatusMessage 模态阻塞解耦:均未执行。 ## 八、文档 - 新增 Documents/refactor_report_2026-10-16.md(本轮详细报告); 删除 Documents/refactor_report_2026-10-15.md(有效内容已并入 agent_mem.md 与本报告)。 - Documents/agent_mem.md 新增 §9.37 与 §11.2 第 85–90 条;更新数据层架构摘要(单一名称字段); §12 历史附录加“时效声明”。 |
||
|
|
1d314ddc24 |
信号面板分组折起动画 + 筛选态折起(项数按筛选计数)+ 悬浮组名可交互与“先滚动再折起” + 适应视图与 Y 轴自适应同源
① 分组折起/展开改为动画过渡(用户要求:被折起的信号向上收起、下方组随之上升、
离组名越远的成员收起越快、末段减速、总时长合理):
- 新增 SignalPanel::m_groupAnims + GroupRowScale():每行速度倍率
1 + 1.2×ordinal/(行数−1)(越靠下越快收尽),叠加 ease-out cubic;
时长 = clamp(115 + 14×行数, 140, 300) ms。
- BuildRows 按比例输出非整行高;低于 0.75 逻辑 px 的行不再产生(不绘制/不命中)。
- 宿主 WM_TIMER(kScrollTimerId) 同时驱动滚动与折起动画;Render 的定时器判定改为
ScrollAnimationActive() || GroupAnimationActive()。
- 新增 SetRowClip/ClearRowClip:动画期间行内容裁到本行矩形(否则 DT_VCENTER
垂直居中的文本会溢出到相邻行)。
② 筛选状态下的分组折起 + 项数按筛选结果计数:
- 筛选模式改用独立的 m_filterCollapsed(默认展开),不再“筛选时忽略折叠状态”,
也不再把筛选里的临时折起污染到普通列表的布局选择。
- ListRow::displayCount 记录当前模式下列出的信号数;组名后方「N 项」只读它
(筛选时 = 该组内符合筛选条件的信号个数)。
- 筛选文本变化 / 清空筛选 / 数据变化 ⇒ 清空临时折起状态 + 进行中的动画 + 挂起请求。
③ 悬浮组名可正常交互 + 点击时“先滚动就位再折起”:
- 根因:HitRow 只按滚动偏移把指针映射到内容位置,而悬浮组名是画在列表顶部的覆盖层
(其自然位置已滚出列表)⇒ 点击穿透到它下面那一行(折叠三角 / 复选框 / ✕ /
名称区域全都点不动)。
- 修法:HitRow 先用 StickyGroupRowIndex() / StickyGroupRowTop() 判定是否落在悬浮行
矩形内,命中则临时打开 m_rowYOverrideActive 并调用新的 RowHitPart() 算行内区域;
同时跳过高度为 0 的行(折起动画中已收尽,否则空行会白白吸走一次点击)。
- 新增 ToggleGroupFromRow():若该行正悬浮(滚动偏移大于该行的内容偏移),先
StartScrollAnimationTo(该行内容偏移, groupIndex) 并把挂起请求记在
m_pendingGroupToggle,滚动到位后才启动折起动画;分组行的单击与双击折叠三角都走
这个入口。滚轮 / 拖滚动条 / 筛选变化 / 切标签页一律清掉挂起请求。
④ 「适应视图」与「Y 轴自适应」彻底同源(单数据点信号):
- 根因:FitSinglePointCluster 给每个网格另算一套幅值区间(“散点半径留白 + 跨度 1”),
而 AutoRangeLaneToView 走 PadValueRange ⇒ 同一视界下两者结果不同;且它显式写
lane.fitYToSelection = false ⇒ 点「适应视图」会把「Y 轴自适应」悄悄关掉。
- 修法:单点簇只负责时间轴(以该时刻居中 + 一大格 1s),逐网格幅值轴与常规路径 /
Y 轴自适应完全同源(fitYToSelection → AutoRangeLaneToView,否则 autoRange = true,
两者之后都接 ApplySinglePointValueMargins),并且绝不改写 fitYToSelection。
- 有意的行为变化:单点信号「适应视图」的幅值跨度由旧的 1 变为 2×max(1,|值|×0.1)
(即与 PadValueRange 一致);§9.33 用例 [A] 里“幅值跨度 1”的期望已作废,
散点仍在网格正中且完整可见(ApplySinglePointValueMargins 兜底)。
验证(离屏 SW_HIDE,不占用鼠标 / 键盘 / 屏幕;进程与线程均 BELOW_NORMAL):
- 新增 .preflight/perf/panelanim_main.cpp(panelanim.exe)94/94 通过(连跑 3 次稳定):
折起/展开的分帧过渡与内容高度单调收敛、越靠下的行越矮、末态精确无残留、
筛选计数与独立折起状态、悬浮组名四个可交互区域全部命中且下层行仍可点击、
点击后“先滚动到位再折起”、真实 MF4-Record.MF4 单点信号下两种自适应逐位一致且往返稳定。
- A/B 三组回退(回退绘图区修复 / 禁用悬浮命中优先 / 动画时长置 0)分别 4 / 13 / 8 项如实失败。
- 既有 verify 49/49、datacheck 69/69、edgecheck 46/46;make clean && make all 零警告。
(顺带修正本机 verify 探针 4 条与远端新规则不符的过期断言:幅值留白改为调用唯一的
PadValueRange、派生信号 offsetTime 断言 0(§9.21 已烘入数据)、拖拽换位按“被拖网格
自身边界”判据的新目标值。)
记忆:Documents/agent_mem.md 新增 §9.35 与 §11.2 第 76–81 条,并更新第 70 条与 §9.33 备注。
|
||
|
|
cb9d3e4b8f |
修复浮动面板双击穿透、网格高亮触发来源、新增样式区「目标」行与联动平滑滚动
用户 2026-10-15 第六轮报告的四个问题与要求(详见 Documents/agent_mem.md §9.34):
1. 在网格内的实验性功能浮动面板上双击任意位置会穿透到下层网格,导致网格触发自适应。
根因:PlotView::OnDoubleClick 在“差值标签 / 光标标签 / 光标线 / 图例”都不命中时
无条件调用 FitAll()。
修法:在该函数最前面(高于一切下层命中)用 TopPanelAtPoint() 判定,命中浮动面板
则直接消费(面板自身双击无额外语义)。确立“上层元素的交互不得影响下层”的规则。
2. 未开启任何工具栏功能时鼠标移到网格上会让整个网格变色高亮(旧需求已不适用)。
修法:取消“鼠标在网格里移动就高亮”的行为;改为在信号选择区域悬停某条信号时,
若该信号已被勾选绘制到某个网格,则该网格变色高亮,并且该信号曲线本身也高亮;
鼠标移开即恢复。悬停未勾选的信号不产生任何高亮。
实现:PlotView 新增 m_externalHoverSignal + SetExternalHoverSignal()(与 m_hoverSignal
分开保存,因为绘图区 OnMouseLeave 会清掉后者,而列表悬停时鼠标不在绘图区内)、
EffectiveHoverLane();SignalPanel 新增 HoveredSignal(),并在 InvalidateRows()
里一并作废;Application::Render() 每帧同步(覆盖 WM_MOUSELEAVE / 数据变化 /
切换标签页等全部路径)。GX 徽标的悬停高亮仍按鼠标位置独立判定。
3. 在信号样式自定义区域最上方(预设颜色色块之上)新增「目标」行。
内容:当前正在被编辑的目标信号名称;若它已绘制到网格,则在名称后方显示所在 GX 标签。
交互:与信号选择区域的信号行一致 —— 名称/GX 标签悬停有高亮提示,且悬停时同样触发
该信号所在网格高亮 + 曲线高亮;单击不进入编辑(只触发联动滚动),双击才进入改名
(复用 StyleEditField::SIGNAL_NAME,m_editRow < 0 表示目标是选中信号)。
实现:SignalPanel 新增 m_targetRowRect / PaintTargetRow / UpdateTargetRowGeometry
(名称宽度与“是否有 GX 标签”都可能在不重排时变化,因此按需重算并带缓存键)、
StyleHit::TARGET_NAME / TARGET_BADGE;DesiredStyleHeight() 与 LayoutStyleControls()
相应多算一行。
4. 联动机制更新:从文件导入大量信号(如 MF4 的 124 条)时,被选中的信号可能被滚动到
可见范围之外而找不到。
修法:新增 SignalPanel::ScrollToSignalAnimated()(+ AdvanceScrollAnimation),
触发联动时把信号列表平滑滚动到目标信号完整可见:
* 目标在视界上方 ⇒ 向上滚动,且顶部让出一个组行高度,避免被悬浮组名遮挡;
* 目标在视界下方 ⇒ 向下滚动到完整露出;
* 已经完整可见则不动;
* ease-out cubic(越远越快、靠近变慢),时长 = clamp(距离×0.35ms, 130, 300),
总时长有上限;用户滚轮 / 拖滚动条立即取消动画。
触发点:绘图区内把某条信号选为当前目标(点击图例、点击信号曲线、双击图例改名)
⇒ PlotView::SelectSignal() 发布请求(TakePanelScrollRequest()),宿主在鼠标消息
处理后调用 ApplyPendingPanelScroll();样式自定义区的任何交互(预设色块、自定义
颜色入口、样式按钮、线宽/透明度滑块与数值框、「系数/偏移」「镜像」二级菜单、
绝对值)以及「目标」行的单击也都会触发同一次滚动。
宿主:Application 新增 16ms 定时器(kScrollTimerId)驱动动画,并由 Render 自管开关
(与提示浮层淡出同一套做法)。
验证(全部离屏,不占用鼠标 / 键盘 / 屏幕):
- .preflight/check(SW_HIDE + 只注入合成消息 + 进程/线程 BELOW_NORMAL)**73/73 通过**,
连跑 3 次稳定:[A] 浮动面板双击不移视界,且对照组(空白处双击)必须触发自适应;
[B] 鼠标在网格里移动不高亮;[C] 列表悬停 → 网格高亮 + 移开恢复 + 未勾选信号不高亮;
[D] 目标行的布局/占位/GX 标签位置与显隐/悬停联动/单击不编辑 + 双击改名(初始文本正确);
[E] 长列表 122 行:动画分多帧、单调、时长上限 300ms、目标完整可见且不被悬浮组名遮挡、
已可见时不动、滚轮取消动画;[F] 样式区交互与绘图区联动都会滚动到目标并完整显示;
[G] 布局回归(目标行在列表下方、样式区仍完整、默认无悬停与高亮)。
- A/B 判别:回退六处实现后 **15 项如实失败**(含"面板双击仍触发自适应"、"鼠标移到网格即高亮"、
"目标行不联动"、"联动瞬移而非动画")。
- make release:全部源文件重新编译 **零警告**;链接用临时 `make release TARGET_FILE_NAME=Target_check` 通过。
同步记忆:Documents/agent_mem.md 新增 §9.34、§11.2 第 72–75 条硬约束、变更日志新行。
|
||
|
|
73389f424c |
适应视图:撤回旧策略并重做边界规则(单值散点让出半径 + 单点簇视图)
用户 2026-10-15 第五轮报告的两个问题与要求(详见 Documents/agent_mem.md §9.33):
1. 加载文件后不勾选任何信号、连点几次「适应视图」,再勾选一个单值信号时,
初次自适应没有发生、数据点既不居中也不在网格内。
根因:FitAll() 在没有任何可见信号时也把 Tab().ViewInitialized() 置为 true,
随后勾选第一个信号时 Application::SynchronizeView 认为"已经初始化",
于是跳过自适应,视界停在"无信号"那一次的整段范围上(实测 MF4 为 42.386s,
数据点被推到绘图区右边缘之外)。
修法:FitAll() 只有在存在可见信号时才置位 ViewInitialized();
并让"初次自适应"与工具栏「适应视图」共用 FitAll() 这一条实现
(不再先试 FitSinglePointSignal)。
2. 仅勾选 WIN_Motor_StallCurrentVolBrk(单值信号)与 m_MOT_Current_DCom[0](连续信号)
时点「适应视图」,单值信号的点被挤到显示区域外只剩半个;上一轮"为左右空出一小格"
的需求效果不佳。
修法(按用户要求先撤回旧策略、再重做规则):
* 撤回并删除 FitSinglePointLanes()(左右各留一小格)、TimeGridCellSeconds()、
LaneValueGridCell()、FitSinglePointSignal()(首个单值信号的时间跨度 1s 视图)。
* 新规则:常规全局适应后,任何一条边界(时间轴左/右、每个网格幅值上/下)
若被一个单值信号的数据点顶住,就把该边界向外让出该散点的半径(+1 像素),
使其完整显示在网格内;只在散点会被裁切时才扩展(内部散点使视界一点不动);
边界不是单值信号的数据点时就落在该信号点上(时间轴不留额外余量)。
判断依据用信号自身的数据点,而不是时间轴范围(MF4 里单值信号与连续信号
常共用同一条时间轴,用轴的范围会误判)。
* 特例:全部可见数据点都是单值信号且时刻完全相同时,时间轴以该时刻居中、
缩放到一大格 = 1s(视界 = kTimeTickTarget × 1s = 10s,此时 NiceStep(10,10) 恰为 1.0),
每个网格的幅值区间以"能完整画出该网格内的全部数据点"为准
(退化成单值时以跨度 1 居中)。
* 新增 FitContribution / CollectFitContributions / FitLaneHeight /
ApplySinglePointValueMargins / FitSinglePointCluster 五个成员承载上述规则。
验证(全部离屏,不占用鼠标 / 键盘 / 屏幕):
- .preflight/check(SW_HIDE + 只注入合成消息 + 进程/线程 BELOW_NORMAL)**62/62 通过**,
连跑 3 次稳定:[A] 初次自适应、[B] 时间边界(外扩量恰为半径 + 1px)、
[C] 幅值边界、[D] 单点簇特例(跨度 10、一大格 1s、每个点都在网格内)、
[E] 回归(普通信号视界 = 数据范围、网格保持自动自适应、§9.31 的
"适应视图 ↔ Y 轴自适应互不干扰"仍逐位成立)。
- [F] 真实文件 TestData/MF4-Record.MF4:WIN_Motor_StallCurrentVolBrk 确实只有 1 个有效点
(t = 0.005492),与 m_MOT_Current_DCom[0](8475 样点)复现用户场景 —— 点「适应视图」后
散点圆心离绘图区左边界 16.5px、半径 15px,完整可见(修复前它正好压在边界上、只剩半个)。
- A/B 判别:回退四处规则后 **20 项如实失败**,失败信息即用户症状
("the dot is horizontally centred (got 1697, want 1084.75)"、
"[F] initial view = one big cell per second (got 42.386313, want 10.000000)")。
- make release:全部源文件重新编译 **零警告**;链接用临时 `make release TARGET_FILE_NAME=Target_check` 通过。
同步记忆:Documents/agent_mem.md 新增 §9.33、§11.2 第 69–71 条硬约束(并把第 68 条与
§9.32 的第四小节显式标注为"已作废/被取代")、变更日志新行。
|
||
|
|
aee3a388b5 |
修复信号面板滚动条跟随、工具栏连点丢状态、图层竖向网格线步长、多网格单点自适应与单点信号拖拽
用户 2026-10-15 报告的五个问题,逐一给出根因与修法(详见 Documents/agent_mem.md §9.32):
1. 拖动信号面板滚动条、鼠标移到绘图区后“滑块卡在原地不动”
根因:Application::WM_MOUSEMOVE 的 plotOnlyMove 只看 inPlot && m_mouseOverPlot,
拖滚动条进入绘图区后判定“本帧只影响绘图区” ⇒ 面板滚动偏移已变却从不重绘。
修法:新增 draggingNonPlot(正在拖非绘图区组件时一律走完整失效)。
2. 工具栏快速连点 / 双击丢状态
根因:(a) 窗口类带 CS_DBLCLKS,快速第二次按下被合成 WM_LBUTTONDBLCLK 而工具栏未处理
(那一次按下完全丢失);
(b) HandleMouseMove 只要鼠标不在工具栏上就调 OnMouseLeave(),清掉 m_pressedIndex
⇒ 按住期间从按钮边缘划出再回到按钮松手不会触发任何动作。
修法:WM_LBUTTONDBLCLK 命中工具栏时按“按下”处理;m_activeTarget == TOOLBAR 期间不调 OnMouseLeave()。
3. 网格叠底:拖拽期间竖向网格线错位 / 丢失,操作信号样式时“闪烁”
根因:PaintLane 的竖向主刻步长由 Tab().ViewMin/ViewMax 推出,而渲染信号图层时该区间已被
RenderLaneLayerRegion 改成“宽窗口”(≈2× 前台)⇒ 图层辅助线步长 2×、位置与前台不同、
“与主刻重合则跳过”的判据也随之错位 ⇒ 贴图后网格线错位 / 整条丢失,
并在“图层帧 ↔ 结算帧”之间来回切换时闪烁。
修法:新增 m_renderTimeStep,由 RenderLaneLayerRegion 用前台视界跨度算好传入
(与纵向 m_wideTickSpan/m_wideTickHeight 同一思路),PaintLane 优先使用它。
4. 多个网格、每个网格只有一个数据点时「适应视图」把数据点甩飞
修法:新增 FitSinglePointLanes(),按用户给出的规则:
* 左右边界 = 所有网格中最左 / 最右数据点的时间,再向外各偏“一个网格小格”的时间跨度;
* 逐网格 Y 自适应,并在纵向上下各留“一个 Y 小格”的高度;
* 全部数据点在同一时刻时 ⇒ 时间跨度 1s 且以该时刻居中(与首个单点信号的初始视图对齐);
* 某网格只有一个取值 ⇒ 该网格 Y 跨度 1 且居中。
仅在全部可见信号都是单点(finiteCount == 1)时启用,否则交回常规 FitAll。
5. 单点信号的悬停高亮与拖拽换网格
修法:HitTestSignal 新增单点分支(SinglePointScreenPos + 容差 = max(常规容差, 该点绘制半径));
悬停用环状光晕且不改鼠标形状(QueryCursor 不查 HitTestSignal),按下拖拽后才改 IDC_SIZEALL;
拖拽虚影对单点信号回退为实心点 + 环。
验证(全部离屏,不占用鼠标 / 键盘 / 屏幕):
- .preflight/check(SW_HIDE + 只注入合成消息 + 进程/线程 BELOW_NORMAL)**67/67 通过**,连跑 3 次稳定。
其中 [C] 直接量“宽基底图层位图自身”的网格线间距(图层主刻 123px vs 前台 122px、辅助线 25px vs 25px),
并断言拖拽帧确实来自宽基底贴图路径(m_wideLastValid)。
- A/B 判别:回退上述六处修复后 **17 项如实失败**([C] 辅助线 49px vs 25px、20 vs 40 条、最大偏移 539px)。
- make release:三个改动文件重新编译 **零警告**;链接用临时 `make release TARGET_FILE_NAME=Target_check` 通过。
同步记忆:Documents/agent_mem.md 新增 §9.32、§11.2 第 64–68 条硬约束、变更日志末行。
|
||
|
|
57cc714c1d |
组名平滑悬浮 + 单点信号初始视图 + 光标吸附统一判定(自动点线同源)
一、信号列表「组名悬浮」平滑化、且不再遮住下一个组名
* 悬浮位置改为 min(列表上边界, 本组最后一行下边界 − 组名高度):
- 本组还有行在列表里时钉在列表上边界;
- 本组最后一行开始滑出上边界时,组名跟着一起上移、随滚动条自然离界
(不再像旧实现那样突然消失);
- 「本组最后一行的下边界」恰好就是下一个分组行的自然位置,因此悬浮组名的下边界
永远不超过下一组名 —— 旧实现固定钉在列表上边界会把下一组名盖住,让别人以为
后面的信号属于上一组。
* 折叠的分组不再悬浮(折叠时本组“最后一行”就是它自己,悬浮位置 = 自然位置,
于是随滚动自然划过,不会停在最上方)。
* 底部边界线只在真的“浮”在其它行之上时才画,避免与正常绘制出现像素差。
二、单点信号的初始视图
* 新增 PlotView::FitSinglePointSignal():当前没有任何信号被勾选、而勾选的第一个信号
只有一个有效数据点时,把该数据点居中放置,时间跨度 = 1s、Y 轴跨度 = 1
(各为一个大格),并关闭该网格的整段自适应以免被覆盖。
Application::SynchronizeView 在首次勾选时先尝试它,失败才走 FitAll()。
* AutoRangeLaneToView 的退化区间(可见区间内只有一个取值)改用与 PadRange 一致的留白
max(1, |v|×0.1),不再产生“几乎为 0”的区间(那会让幅值网格线挤成一团、看不出数据点)。
* 需求作废:上一轮“2…16 个有效采样点默认改为点线 + 2px”已取消(放大自动点线已覆盖该场景);
只保留“只有一个有效采样点 → 点线 + 最大线宽”。
* SignalData::singlePointTime 升级为 sparseTimes/sparseValues(有效点 ≤ 1024 时在加载/插入
时缓存全部有效点),同时服务单点初始视图与光标吸附。
三、光标吸附的统一判定
* N = GridMinorCellCount():网格底衬一横排的小格数,与网格绘制用同一套
NiceStep + 五等分算出(因此不随缩放等级大幅变化)。
* SignalSnapEnabled():当且仅当某信号在当前一屏内的数据点个数 < N×4 时为它开启吸附;
判据随视界实时重算 ⇒ 疏密变化的信号在稀疏区自动吸附、移回稠密区自动自由移动。
额外要求视界内至少有一个可见数据点(否则“0 < N×4”会把数据全在视界之外的信号
误判为稀疏)。统计只在可见下标区间内进行且攒够阈值即早退,代价有界。
* SnapTimeToSample():判定只针对鼠标所在网格(同一个时间轴上不同网格结果可以不同);
同一网格内所有符合判定的信号的数据点都参与吸附比较;
吸附半径 = 一个网格小格的宽度,且不超过半个局部采样间距 ⇒ 数据点很稀疏
(一屏只有一两个点)时光标在点之间仍可自由移动(吸附是提示,不是卡住)。
* 删除旧的 SampleSpacingPixels(6px 下门限 + 半屏宽上门限)。
四、放大自动切点线改为与吸附判定完全同源
* UpdateAutoScatterStyles() 改用 SignalSnapEnabled:进入判定自动切「点线」,
不再满足(适应视图、或视界从稀疏区移到稠密区)自动恢复原样式;
自动期间用户手动改过则不恢复(尊重用户选择,判定放在恢复之前)。
* 删除旧的 AxisSparseInView(“采样间距 > 半屏宽”)。
五、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
55/55 通过(连跑 3 次稳定):[A] 组名悬浮(钉住/平滑上移/整组滚过后换下一组/折叠组不悬浮/
悬浮下边界不超过下一组名)、[B] 单点初始视图(跨度 1s 与 1、居中、点线 + 最大线宽、
两信号时不再走单点路径)、[C] 吸附判定(满视图都关闭/稀疏窗口开启/无可见点不开启/
同一时间轴不同网格结果不同/同网格多信号的点都参与/稠密↔稀疏双向动态切换/
单点靠近吸附远离自由)、[D] 自动点线(同源判定/恢复/用户手动改过不覆盖)。
A/B:临时禁用「悬浮位置公式 / 单点初始视图 / 吸附判定」后重跑 ⇒ 14 项如实失败。
make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。
六、规则同步
Documents/agent_mem.md 新增 §9.30、§11.2 第 58–61 条硬约束与变更日志。
|
||
|
|
6eedbe3c5d |
数据层大重构:文件→分组 / 时间轴一等公民 / 加载器抽象 + CSV 多时间轴 + MF4 支持
一、数据模型解耦(详见 Documents/refactor_report_2026-10-15.md 与 agent_mem §9.27)
1. DataFile → SignalGroup:删除 filePath / fileName / time / timeColumnName / timeMonotonic /
minTime / maxTime —— 「分组」是应用内的逻辑容器,加载完成后与磁盘文件再无关系。
2. 新增 TimeAxis(name / time / minTime / maxTime / monotonic / finiteCount),一个分组可含 1..N 条;
SignalData 新增 axisIndex 指向所属时间轴,采样值与时间的对应关系自包含。
3. SignalData 删除冗余的 rawHeader(name 的副本)与 unit(由列名猜出、全仓无人使用);
名字只保留 name(源名称)+ displayName(用户重命名),Title() 统一展示。
4. SignalRef::fileIndex → groupIndex;DataSet:AddGroup/RemoveGroup/GroupAt/GroupCount/Groups()/
SetGroupDisplayName/RemapSelectionAfterGroupRemoval + 新增 Axis(ref);
TabConfig:SetGroupVisible/GroupHasVisible/GroupAllVisible/RemapAfterGroupRemoval +
RowKind::GROUP / CMD_GROUP_*。
5. 采样与统计 API 首参由 const DataFile& 改为 const TimeAxis&:
SampleSignalAt / SampleDisplayedSignalAt / FindNearestSample(Utilities)与
SampleSpacingPixels / VisibleIndexRange / EdgeIndexRange / MeanFilterValueAt / WeakPeakValueAt /
EdgeCountsFor / EdgeCenterCountsFor / EdgeScanFor(PlotView)。
6. 视图层:ResolvedSignal = { data, style, group, axis, visible };PolylineCacheEntry::file→group;
EdgeScanState::file→axis(统计身份 = 时间轴 + 信号);
ComputeVisibleTimeBounds 只统计可见信号真正用到的时间轴。
二、加载层抽象(格式与模型彻底解耦)
- 新增 Utilities/Inc/DataTable.hpp:DataTable(通用列优先表格)+ LoadProgress/Info +
TableBlock(一个时间轴块)+ LoadedSource(源文件全部块)。
- 新增 Utilities/Inc|Src/DataLoader.*:唯一入口 LoadSignalSource 按扩展名分发 +
IsSupportedSignalFile + SignalFileDialogFilter。
- 新增 Utilities/Inc|Src/CsvLoader.*:CSV 解析 + 多时间轴切分 + 各轴按自己的时间列裁剪。
- 新增 Utilities/Inc|Src/Mf4Loader.*:ASAM MDF v4 解析。
- 删除 Utilities/Inc/CsvTable.hpp 与 Utilities/Src/CsvTable.cpp。
- 唯一装配点 BuildSignalGroup(LoadedSource&&, paletteBase, group, error, progress):
时间轴去重合并 + 信号命名 + 调色板取色 + 统计与粗粒度包络;新增格式只需写一个加载器。
三、CSV 多时间轴(修正用户报告的缺陷)
- 只有名为 t/time/timestamp/simulation time/时间/时间轴 的列是时间轴(判据收紧,
不再用「包含 time」以免把 timeout 误判成时间轴)。
- 每个时间列开启一个新块,其后直到下一个时间列之前的列都属于该块;各块按自己的时间列裁剪
⇒ 支持各轴长度不同(Multi_Timeline.CSV 实测切出 3 条轴,长度 7/4/7);
**不再把其它 time 列当成信号显示**。
- 完全没有时间列时保留旧行为(第一列当时间轴,单轴)。
四、MF4 支持(新功能)
- 加载后新建一个分组、组名 = 文件名,文件内全部信号归入该分组。
- 每个通道组(CG)是一条时间轴,内容相同的时间轴自动合并(实测 27 个 CG → 3 条轴 / 124 条信号)。
- 实现 MDF v4 块解析(HD/DG/CG/CN/CC/DT)、1–64 位整数(任意位偏移、含符号扩展)与
32/64 位 IEEE 浮点解码、无效位(inval bit)处理、线性换算 phys = cc_val[0] + cc_val[1]*raw。
- TX 文本块同时兼容规范写法(U32 长度 + UTF-16/UTF-8)与 CANape 的「无长度前缀 NUL 结尾裸文本」。
- 未实现(跳过该组):压缩块 ##DZ/##HL/##SD、##DL 数据列表、字符串/MIME 通道。
五、顺带修复
- Application::Initialize:窗口类已注册(ERROR_CLASS_ALREADY_EXISTS)时复用而不是弹模态错误框
(旧实现让同一进程第二次构造 Application 永久阻塞);Shutdown 注销窗口类并销毁窗口。
- UI 文案统一到「分组」:选中该分组全部信号 / 取消该分组全部信号 / 关闭该分组(从工作区移除)/
请关闭部分分组或信号后重试。/ 尚未加载数据,请打开数据文件 / 打开数据文件以查看波形 /
(可同时加载多个文件,各自成为一个分组);对话框过滤器增加 *.mf4/*.mdf。
六、验证(全部离屏:BELOW_NORMAL 优先级 + SW_HIDE 窗口 + 只注入合成消息)
- 新增 .preflight/perf/datacheck_main.cpp:69/69 通过(单轴 CSV 回归、多时间轴 CSV、MF4 组名/信号数/
时间轴/主时间轴 ns→s/setSpeed 首样本与原始字节人工核对、DataSet 不变量、派生信号沿用源轴、
MF4 集成 124 信号并一格 60~68ms/帧)。
- 新增 .preflight/perf/edgecheck_main.cpp(ASCII-only,替代被编码破坏的 panelcheck):46/46 通过
(「调整」符号规则走真实菜单路径、边沿统计按帧推进/不饿死/逐位一致/无鼠标输入也完成、
面板两列等宽与信号名限宽、弱峰复位控件)。
- 既有 .preflight/perf/verify_main.cpp(已适配新 API):47/47,另有 2 条本机未同步的旧规则断言
(derived offsetTime kept / mouse in lane1 lower half),与本轮无关。
- make clean && make all 零警告。
|
||
|
|
375a15c8c2 |
派生曲线内存治理(弱峰调整等)+ 边沿统计改为统一预算状态机(含中心轴)+ 离屏测试零打扰
一、派生曲线内存治理(按上一轮报告的方案实施;绘制性能优先,同时为小内存设备留出空间) 1. PeakExtremum 字段重排(double value + uint32 index + int8 type):24 → 16 字节。 编码器方波这类极值密集信号省约 1/3 的极值表内存,且**不改变任何数值结果** (value 保持 double,改成 float 会让结果与历史版本不一致): 实测 3M 采样单次计算的工作缓冲峰值 33.23 → 26.73 MiB。 2. ComputeDerivedWindow 对「整段窗口」不再复制采样(直接引用原向量): 8 B/采样 × 2580 万 ≈ 206 MB/条,而“预览曲线一律按整条信号计算”正是最常见情形。 3. 大窗口任务的并发上限由内存压力决定(CurveJob::hugeLimit:内存充足 2、紧张 1): 单条整段曲线工作缓冲约 620 MB(2580 万采样),原先最多 4 条并发 → 峰值 2.5GB+; 实测 4 并发 3M 采样 412 → 324 MiB,且外推值不再随线程数放大。 4. 内存严重不足时**直接拒绝计算**(DerivedMemoryAllows:可用内存 < 输出曲线本体 + 工作缓冲的 最小需求)并只告警一次(新增 kMessagePlotLowMemory;宿主弹一次明确的提示框)。 界面保持可用,绝不会因内存不足抛 bad_alloc 逸出线程导致进程终止。 5. 低内存模式“少量多次”:throttle = true 时并发降为 1、且每阶之间 Sleep(1),慢慢算完。 内存充足时不做任何节流,尽可能快。 6. 取消标志贯通到算法内部(ComputeWeakPeakAdjust / ComputeMovingAverageIterated 新增 cancel 参数,每阶 / 每 1M 采样检查一次)⇒ **过时任务立即退出并归还工作缓冲**; PauseDerivedAsync / StopCurveWorker 先 m_curveCancelAll = true 再 join。 7. AvailablePhysicalBytes() 结果按 200ms 缓存(绘制路径上不再每帧多次系统调用)。 8. 极值表按 count/8 预留容量(try/catch(bad_alloc) 保护),避免 push_back 反复扩容的 1.5x 峰值。 9. MeanFilterCurveFor / WeakPeakCurveFor(整条同步计算)现在只有测试使用; 产品代码一律走 CurveSpanFor(窗口化 + 异步)。 二、边沿统计:统一「逐块累积 + 每 tick 预算」状态机(普通边沿计数与中心轴共用) 1. EdgeScanState + EdgeScanFor 成为唯一实现,EdgeCountsFor / EdgeCenterCountsFor 是薄包装; 删除 EdgeCenterCountEntry / m_edgeCenterCountCache。 2. **未完成时主动请求下一帧**(RequestEdgeScanFrame → PostMessage(kMessagePlotDerivedReady)): 修掉“鼠标不动就一直显示统计中,只有把鼠标移到面板上才更新”(根因就是没有重绘 → 预算不推进)。 3. 阈值/参数变化就地把状态重置(不新建条目,避免拖动时状态爆炸);重置当 tick 不展示部分值 (修掉“除了最后一个信号显示了一个错误的且不更新的数字,其它变成统计中”); 稳定后展示递增的部分值 + “…”;扫完展示精确值(统一走 EdgeCountsText)。 4. 中心轴判定所需曲线**只在后台计算**:只在 AcquireCoveringCurve 命中(键完全相同)时扫块, 否则 ScheduleCurveJob 后本帧不推进。原先该调用没传 allowAsyncCompute(默认 false)⇒ 首帧 / 参数变化后同步算整条均值滤波曲线 ⇒ 界面停顿数秒(用户报告“一改参数整个程序卡住很久”)。 5. MeanFilterValueAt(光标读数)不再调整条同步的 MeanFilterCurveFor,改为窗口化 + 异步。 6. ScheduleCurveJob 改为幂等(m_curveRunningKeys 集合 + 队列去重):统计侧会每帧请求同一份 整条曲线,若每次都当作新请求,正在算的那份立刻被判过时 → 请求→作废→重请求 死循环。 7. 扫描热路径加“无变换/无镜像/非中心轴”的紧凑循环:单次预算耗时 18ms → 1.3ms。 8. 实测(合成方波):6M 采样首帧 1.4ms(部分值)、单次最坏 2.1ms; 拖动横向光标 10 次查询 1.07ms;**同一帧内 5 条信号合计 1.3ms**(预算跨信号共享); 保持不动无需移动鼠标即收敛到与参考实现逐位一致。 三、离屏测试绝不打扰用户的前台工作 - .preflight/uitest 与 .preflight/probe 的 main 开头设置 SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL), 再长的测试也不会与用户的编辑/浏览抢 CPU(此前“打字不顺畅”的根因)。 - 继续保持:窗口 SW_HIDE、只注入合成消息(不动真实鼠标键盘)、每次注入后立即 ReleaseCapture (SetCapture 是系统级的,不释放会把用户的鼠标输入吸进测试进程)。 四、验证(全部离屏) - .preflight/uitest 新增/强化:预算推进首帧不显示部分值 → 第二帧显示递增部分值 → 保持不动收敛到精确值;未完成时会 PostMessage 请求下一帧(直接断言); 拖动阈值期间 complete == false、不显示部分数字、总耗时 < 60ms; 5 条信号同帧合计 < 120ms(实测 1.3ms);中心轴与参考实现逐位一致且无需鼠标移动即可完成。 - 合计 751/751 通过(连跑 3 次稳定);make release 零警告。 规则同步:Documents/agent_mem.md 新增 §9.23(含内存治理表与实测数据)、§11.2 新增第 32–38 条硬约束、 变更日志补一行。 |
||
|
|
d6d3fcd6c1 |
新标签页分组默认折起 + GX 徽标悬停不改光标 + 标题栏 RAM 改私有工作集 + 边沿计数改为预算推进
一、新建标签页的信号分组默认折起
- Application::NewTab(bool collapseGroups):启动时的首个标签页传 false(加载文件后可直接勾选
信号),用户新建的标签页(Ctrl+T / 标签栏 ⊕ / 标签栏右键)传 true。
- TabConfig::SetGroupsCollapsedByDefault(bool) + SetAllCollapsed(bool)(新增);
SyncToDataSet 里 m_collapsed.resize(fileCount, m_groupsCollapsedByDefault)(原来固定 false)——
只影响之后新出现的分组,已存在的分组保持用户当前选择;ToggleCollapsed 补建状态时同样用它。
二、网格 GX 徽标:悬停不改变鼠标形状
- PlotView::QueryCursor 里 GX 徽标分支由 IDC_HAND 改为 return nullptr(= 保持箭头),
仍然保持"提前 return"的位置以免被光标线/图例判定抢走优先级;
只有拖拽中(LANE_REORDER)才是 IDC_SIZENS。
三、标题栏 RAM 口径:总工作集 → 私有工作集(任务管理器"内存"列)
- 症状:加载大文件再关闭分组后,标题栏仍显示几十/上百 MB,任务管理器只有几 MB。
- 根因:原实现取 PROCESS_MEMORY_COUNTERS::WorkingSetSize(总工作集),它把映射进来的 DLL /
共享页面以及已释放但尚未被系统回收的驻留页面都算进去;任务管理器"内存"列的口径是私有工作集。
- 修法:ProcessWorkingSetBytes() → ProcessPrivateWorkingSetBytes(),用 PROCESS_MEMORY_COUNTERS_EX2
(MinGW 的 psapi.h 没有该类型 → 在 .cpp 里就地声明,布局与文档一致),失败时依次退回
PrivateUsage(私有提交)→ WorkingSetSize。
- 实测(离屏探针):加载后 total/priv/commit = 117.55/109.96/138.28 MiB;
关闭分组后 = 15.44/7.62/8.27 MiB ⇒ 内存没有泄漏、完整归还,用户的差异来自口径。
四、边沿计数(EdgeCountsFor)改为「逐块累积 + 每 tick 预算推进」
- 量化(探针):改前 6M 采样 = 26.8ms / 次阈值变化 → 25.8M 采样 ≈115ms/条;
横向光标拖动时阈值每帧都变 ⇒ 单条信号每帧 115ms,5 条信号同一网格 = 575ms/帧
(与用户"开启即卡顿、之后光标几乎拖不动"完全吻合)。
- 新模型:
* 采样对 k(采样 k-1 → k)按 kEdgeBlockSamples = 8192 分块,每块各存上升/下降沿数;
scannedPair = 下一个待扫描的采样对([1, scannedPair) 已扫完)。
* 查询区间:内部整块用块和相加,两端边界块就地精确扫描(≤2 块,代价固定)⇒ 拖动时间光标的
单帧代价与区间长度无关(单调时间轴下区间不进键);非单调时间轴无法用下标区间表达 ⇒ 区间进键。
* 每 tick 预算 kEdgeScanPairsPerTick = 1200000(GetTickCount64 分桶,同帧多次调用共享);
预算用完返回当前部分值,complete 出参告诉界面"还没算完"。
* 拖动横向光标(EDGE_MOVE)期间不推进(阈值每帧都变,推进纯属白做功)→ 返回"最新的完整条目"
的结果(数字稳定)+ complete = false。
* 快路径:单调 && 无镜像 && 无调整变换时用紧凑无分支循环直接比较 values[k] 与阈值
(NaN 与任何数比较都是 false ⇒ 天然满足"NaN 不参与且打断连续",无需 isfinite 分支)。
- 界面:新增 EdgeCountsText(rising, falling, complete) 统一生成文本(完全未算出显示"统计中…"、
部分结果追加" …"),EdgePanelWidth 与绘制都改用它,宽度按实际文本测量避免数字被截断。
- 实测(改后):6M 采样首次调用 1.7ms(部分值)、5 帧内扫完且与参考实现逐位一致;
单次调用最坏 1.9ms;拖动中连查 10 个阈值合计 0.11ms;区间查询在扫描完成后一次调用即精确。
- EdgeCenterCountsFor 未改(它的曲线窗口本来就被 CurveSpanFor 限制,不是本次报告的场景)。
五、内存与性能排查报告(第 4 项按用户要求只排查、未改动)
- 泄漏审计:RemoveFile 路径会 PauseDerivedAsync(清 m_curveEntries/m_curveJobs/m_curvePins)
→ InvalidateDerivedCaches(清折线/边沿/均值/弱峰/场景缓存),DataSet::RemoveFile 用
"前移 + pop_back + shrink_to_fit" 立即归还采样缓冲;实测三种内存口径全部回落到十几 MB 以下。
- 弱峰调整内存暴涨(峰值 4.4GB):实测工作缓冲 ≈34 B/采样(主导项 = 极值表 PeakExtremum 24 B/极值,
编码器方波极值密集 ⇒ 接近 24 B/采样),3M 采样单次 ≈103 MiB、4 条工作线程并发 +412 MiB;
按 25.8M 采样外推 ⇒ 单次 ≈0.9GB、峰值 ≈3.5GB,再加数据本体 1.03GB 与曲线缓存预算 ≤768MB
⇒ 4~5GB,与用户观测吻合。
- 现有机制只解决"算完/关掉之后归还"(ReuseBufferScope / TrimDerivedCurveDataIfIdle /
kHugeSignalPreviewSamples / 曲线缓存字节预算),没有任何"峰值上限"与"优雅降级":
全仓没有 try/catch,重分配失败抛 std::bad_alloc 而重计算都在 std::thread 里
(CurveWorkerLoop / RunAsyncLoad),异常逸出线程入口 ⇒ std::terminate() ⇒ 进程终止。
1GB 剩余内存时:先大量换页(系统卡顿),提交失败则直接崩溃。
- 建议(未实施):①超大信号派生计算串行化 ②极值表字段瘦身/分块流式 ③废弃任务尽早退出
④GlobalMemoryStatusEx 自适应降档 ⑤线程入口加 try/catch。
六、验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/probe(诊断探针):内存口径与泄漏审计、边沿计数扫描耗时、弱峰工作缓冲峰值
(带 1ms 峰值采样线程)。
- .preflight/uitest 新增 [AR](边沿计数预算/精确性/区间查询/拖动期 O(1)、GX 徽标悬停光标)与
[AS](标题栏 RAM 口径 = 私有工作集);并给菜单类用例加了关闭残留菜单 + 允许补点的卫生逻辑。
- 合计 738/738 通过(连跑 5 次约 1 次出现既有 harness 抖动:菜单点击类用例,重跑即好);
make release 零警告(Target.exe 被占用时链接报 Permission denied,用临时 -o 名字验证链接)。
规则同步:Documents/agent_mem.md 新增 §9.22(含内存与性能排查报告),§11.2 新增第 26–31 条硬约束,
变更日志补一行。
|
||
|
|
a2a38236fe |
边沿计数改用「绘制值」+ 派生信号烘入时间偏移 + 图例托底统一为 GX 徽标款 + 调整二级面板向上展开
一、两个「边沿计数」按「画出来的曲线」判定(原先按原始采样值 → 应用过「调整」的信号恒为 0)
- 用户复述的权威定义:设定一个横向(Y 轴)光标,信号绘制出来的曲线从下往上穿过该幅值 = 上升沿,
从上往下穿过 = 下降沿。
- 症状:VERY_BIG_DATA 的「编码器B相」设系数 50 + 时间轴偏移 -20s,把光标拖到画面中间,
很规整的方波却统计出 0/0。
- 根因:EdgeCountsFor / EdgeCenterCountsFor 只比较 signal.values 的原始采样值,而横向光标读的是
屏幕上的值(已乘系数、已加偏移)→ 原始值域与阈值完全不相交。
- 修法:判定值一律取 ApplyDrawTransform(style, values[SourceIndexFor(style, count, index)]);
* 中心轴两侧必须套用同一套变换(drawn - drawnCenter);
* 水平镜像时曲线数组按源下标存储、而统计区间是显示下标 → 请求曲线窗口前必须把
[needFirst, needEnd) 映射为 [count-needEnd, count-needFirst);
* 缓存必须带样式指纹:EdgeCountEntry / EdgeCenterCountEntry 新增 styleKey
(EdgeStyleKey():gain/offsetY 按 1e6 定点 + absolute/mirrorVertical/mirrorHorizontal 位),
否则改完「调整」仍会命中变换前的旧计数。
二、时间区间之外的采样不得计入(区间边界点)
- 症状:A 在阈值以下、B 超过阈值;双光标下一个光标正好在 B 点,旧实现把 B 算成一个上升沿。
- 根因:EdgeIndexRange 在左端多取了一个采样点(if (begin > 0) --begin;)。
- 修法:删掉该外扩,收紧为「只有两个端点都落在 [begin, end) 内的采样对才计入」
(begin = lower_bound(rangeMin)、end = upper_bound(rangeMax),调用方从 begin+1 枚举到 end-1)。
- 结果:把区间起点从 times[crossing-1] 移到 times[crossing] 恰好少一个上升沿。
三、派生信号把 offsetTime 也烘进采样值(推翻 §9.19 的「唯一例外」)
- 用户要求:新信号默认不启用任何「调整」时就必须与网格里的预览逐点重合。
- 修法(Application::BuildDerivedSignal,在「值变换 + 水平镜像反序」之后追加一步):
新信号第 i 个采样 = 已变换的曲线在 time[i] - offsetTime 处的取值(取最近的采样点);
单调时间轴用只前进的游标(整段 O(n));平移后越界的采样置 NaN(与预览一致);
新信号样式全部保持默认(gain=1 / absolute=false / 两个镜像=false / offsetY=0 / offsetTime=0)。
- 退化路径:时间轴非单调或时间列缺失时无法按下标推进取样,退回把 offsetTime 带在样式上
(两条路径的显示效果相同)。
四、图例托底统一为 GX 编号徽标同款 + 新增更小字体 uiTiny
- 托底高度 = kLaneBadgeHeight、上下边界与 LaneBadgeRect() 完全一致、圆角 = 高度的一半(胶囊形)、
配色与描边与徽标相同(选中态 accentSoft/accent)、水平内边距 = 胶囊半径。
- 文本改用新增字体 UiFonts::uiTiny(10 逻辑 px):原 uiSmall(12px) 几乎顶满 17px 高的胶囊。
- LegendItem 拆出 plateWidth(托底宽 = 内容宽 + 2×内边距),条目步进 = plateWidth + kLegendItemGap;
kLegendPlateRadius 已删除(半径一律由徽标高度推出)。
五、「系数/偏移」「镜像」二级面板改为向上展开
- PopupMenu::Open(..., bool expandUp = false):true 时 (x, y) 是菜单的左下角、菜单向上展开;
BuildLayout 增加同参数并记录 m_buildExpandUp(RebuildLayout 必须复用,否则数值回写触发的重排
会让面板跳回向下展开)。
- SignalPanel::OpenGainMenu / OpenMirrorMenu:锚点 = (button.X, button.Y - kAdjustMenuGap),
即「面板左下角对准按钮左上角、再略微上去一点」(kAdjustMenuGap = 4 逻辑像素)。
六、验证(全部离屏,不占用用户的鼠标 / 键盘 / 屏幕)
- .preflight/uitest 新增 [AR]:
* 边沿计数 plain / gain=50 / offsetY / mirrorVertical / mirrorHorizontal 逐个与「独立参考实现」
逐位一致;offsetY 用例给出 A/B:按原始采样值算会得到 7/6 个边沿(正是用户报告的缺陷);
* 区间边界:起点从 crossing-1 移到 crossing 恰好少一个上升沿;
* 中心轴:absolute + offsetY 的样式下与参考实现逐位一致(32234 个边沿量级);
* 派生信号:无偏移时全部 128 万采样逐点等于 transform(滤波);带 +10 个平均采样周期的偏移时,
抽样点等于 transform(滤波) 在 time[i] - offsetTime(最近采样)处的取值;样式全默认;
* 图例托底:height == 徽标高度、Y == 徽标 Y、radius == height/2、色块与文本都在托底之内;
* 调整面板:菜单左边界 == 按钮左边界、菜单底 == 按钮顶 - 4*scale、且不覆盖按钮(两个按钮各测一次);
* GX 拖拽让位:±0.6H 各让位 1 格(目标 0 / 3),±0.4H 与恰好 ±0.5H 都不让位(严格「超过」)。
- 同步修正 harness 里两处「旧规则」断言(否则必然 FAIL 并误导后来者):
[AP] 「派生信号继承调整变换」→ 「样式默认 + 采样值按 time[i]-offsetTime 烘焙」;
[AN] 「图例带无托底(网格线可透出)」→ 「装饰矩形逐块、无一块跨越整条带、未被托底覆盖的列
仍能看到底层内容」。
- 合计 722/722 通过;make release 零警告(本机 GCC 15.2.0 UCRT;Target.exe 被占用时链接会报
Permission denied,已用临时 -o 名字验证链接正确性)。
规则同步:Documents/agent_mem.md 新增 §9.21,§9.19 的「offsetTime 仍需继承」标注为已作废,
新增 §11.2 第 21–25 条硬约束,并补变更日志一行。
|
||
|
|
7c32eca004 |
修复自适应/首帧卡顿回归 + 派生信号烘值 + 图例交互与托底重做
一、性能(详见 agent_mem §9.19) - AutoRangeLaneToView:把 DataSet::Style(ref) 从逐采样循环里提出来(远端新增的「调整」 功能让它每个采样点解析两次,实测 147ms 劣化到 1200ms),并在「视界覆盖整条信号且 无任何数值变换」时直接取信号已缓存的 min/max(O(1))。工具栏「Y 轴自适应」在全视图下 1200ms → 0.02ms,结果与逐点扫描逐位一致;有变换时仍逐点套用与绘制相同的变换。 - 新增 WarmLaneLayersStep():加载后宽基底图层从未分配,用户第一次平移/选区放大时 Paint 会在那一帧同步重建全部网格图层(首帧 64.3ms)。现在改为在「命中静态场景缓存」 (视界未变、非拖拽)时用既有预算机制每帧至多补 1 个网格,把整批重建摊到静止帧; 选区放大首帧 64.3ms → 13.0ms。 二、派生信号 - 「构建为新信号」把源信号的「调整」变换(系数 → 绝对值 → 垂直镜像 → Y 偏移,以及 水平镜像=数据反序)烘进派生采样值,新信号样式一律保持默认(不再是"继承的指纹"); 仅时间轴偏移 offsetTime 因派生信号与源信号共用同一个文件时间轴仍需继承。 三、交互 - 网格 GX 拖拽让位判据改为「鼠标指针」:指针越过相邻网格的中心水平线即让位并出现吸附 提示(原先按"被拖网格的中心",要求把整个网格拖过去一格才吸附)。 - GX 徽标悬停高亮:徽标本体在装饰缓存里被冻结,高亮改为贴图之后的叠加层,并新增 m_hoverBadge 状态驱动重绘/取消;QueryCursor 在徽标上返回 IDC_HAND —— 与左侧信号列表 里同一个 GX 徽标一致。 - 修复「中键平移过视界之后图例悬停/点击/双击改名全部失效」:整幅重绘路径会清空 m_legendHits,而装饰缓存命中时 PaintLaneLegend 提前 return 导致永不重建。现在把该函数 拆成「几何 → 命中/装饰矩形 → 绘制」三段,任何路径都先重建命中矩形。 四、绘制 - 每个信号图例单独加圆角托底(配色/圆角风格与 GX 徽标一致);装饰矩形改为逐块 (徽标一块 + 每个条目一块 + 每个 Y 轴胶囊一块),不再覆盖整条图例带;每块托底先用 不透明底色铺满「外接整数像素再外扩 1px」的矩形再画圆角 —— 消除「GX/图例周边旧波形 像素残留」(逐像素验证:装饰矩形内 0 差异)。 - 图例悬停高亮改为不透明托底并在其上重绘色块与文字(否则不透明托底会把图例内容盖住), 同时保留对应信号的曲线光晕;Y 轴刻度胶囊补不透明外接底色,消除同类残留。 验证:离屏 Application harness(窗口 SW_HIDE + 直接注入鼠标消息 + 从后备缓冲区截图, 不占用鼠标/键盘/屏幕)40/40 通过;zoomcheck 新增 mode 20-26 量化;make release 零警告。 |
||
|
|
fc77b0262f |
「调整」行:系数/偏移 按钮 + 偏移调整(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 零警告。 |
||
|
|
c3343907ec |
标题栏内存占用 / 样式区增益与取绝对值·中心反转 / 拾色器排版 / 图例去托底与命中优先级
标题栏: - 应用名称后追加「 | RAM : <值>」:竖线两侧各留两个空格(与分组标题「组X | 组名」的间距风格一致)。 - 数值按 KB/MB/GB 自适应、保留两位小数(复用 FormatByteSize);用 K32GetProcessMemoryInfo 取本进程 工作集(与任务管理器「内存」一致,无需新增链接依赖);1 秒定时器刷新,数值未变时不调用 SetWindowTextW。 信号样式自定义栏: - 线宽与透明度交换上下位置(线宽在上、透明度在下)。 - 新增「系数增益」:输入框(默认 1,只接受数值,范围 ±10000),点击进入编辑、回车/点击别处提交。 - 新增「取绝对值」「中心反转」两个同行的切换按钮。 - 三者作用顺序固定为「系数增益 → 取绝对值 → 中心反转」(中心反转最后结算),实现为 ApplyDrawTransform(style, value),在 BuildPolyline(逐点描边与像素列包络两条路径,含粗粒度分组层)、 派生预览曲线与光标取值读数统一调用;样式属于信号本身(跨视图共享)并进入 SceneContentHash / SceneStyleEntry / 折线缓存键,改动会立即重绘。 - 非保序变换(取绝对值 / 反转 / 负增益)会回退到逐采样点变换、不再使用粗粒度包络层,保证包络精确。 自定义颜色面板: - 把底部的「色号 + 预览色块」移到 RGB 滑块上方;右侧一列(预览行 + 三行滑块)与左侧「色域方块 + 色相条」高度严格相等,两列上下边对齐,面板不再为它单独保留一行。 网格内: - 去掉图例与 GX 编号下方的整块不透明托底(它会把网格线整片擦掉,看起来像一个"可点击控件", 而其右侧空白实际并不响应点击)。 - 图例与 GX 徽标改为优先命中:先于时间光标线 / 光标读数 / 边沿计数横向光标 / 选区放大判定, 因此选区放大模式下也能正常拖拽 GX 标签与点选图例(浮动面板仍然优先,与绘制层序一致)。 验证:离屏 harness(.preflight/uitest,SW_HIDE、不动鼠标键盘屏幕)新增 [AK] 标题栏内存读数(格式与竖线间隙、单位阈值、两位小数、随 256MB 分配增长、定时器刷新); [AL] 样式区行序与几何、增益输入提交、两个切换按钮的真实点击、ApplyDrawTransform 顺序,以及 BuildPolyline 顶点范围与变换后数据范围逐项对齐(默认 / 增益 2 / 取绝对值 / 中心反转 / 负增益+取绝对值+中心反转叠加); [AM] 拾色器两列严格等高(140.00 == 140.00); [AN] 选区放大模式下 GX 拖拽重排 + 图例点选 + 网格线穿过图例带(托底已移除)。 另修正旧用例 [AI](远端已删除"光标跟随视界",改为断言光标时间位置不变、允许滑出视窗)。 合计 530/530 通过;make clean && make all 零警告。 |
||
|
|
ff3319b842 |
修复上下拖动时的绘制异常/抽搐(纵向预渲染补齐)+「弱峰增强」升级为「弱峰调整」(-200%–+200%)
一、纵向绘制异常/抽搐的根因 上一轮给图层加了「上下各半个网格高」的纵向预渲染余量,但"偏移超出余量"时仍走**排队整块重画** (一帧只补一个网格):重画生效前 BlitWideBuffer 只能贴出有像素的那一部分,其余行留在底色上 = 网格内出现空白条带("绘制错误");且重画后的锚点与"贴图偏移"不同源 = 波形"没按预期移动"。 鼠标按在最上方网格一路拖到窗口最下方(离开网格很远)时,每帧的幅值位移都远超余量 → 每帧触发整块 重画 = 抽搐;左右拖动时若鼠标同时有纵向位移,同样触发。 二、修法:与横向完全同构的纵向滚动 - RenderLaneLayerRegion 增加 stripY/stripH(纵向条带),可只补画"新暴露的一整条"。 - RollWideBuffer 新增纵向滚动:BitBlt 整块上/下移 roll 行 → 补画暴露的那一条 → layer.anchorYMax -= roll / pxPerValue(与横向 wideMin 同一套记账)。 必须在横向补条之前(横向补条会用当前 yMax 重置图层锚点,顺序反了细条会带上错的纵向偏移)。 - 滚动量量化(kLayerRollQuantum = 16 像素):不足量子的偏移不滚,交给贴图的 srcY 偏移处理, 使画布映射在拖动中保持稳定、折线缓存(键含幅值映射)得以命中。 未量化时"每帧滚 1~30 行"会让折线每帧重算(实测滚动 3~4ms + 整帧 10~14ms)。 三、「弱峰调整」(原「弱峰增强」) - 术语统一:ComputeWeakPeakAdjust、WeakPeakItem.amountPercent(-200–200,默认 150)、面板标题 「弱峰调整」、标签「调整幅度 %」、光标胶囊尾缀、构建信号名「<原信号> (弱峰调整 X% M次迭代)」。 - 滑块量程 -200% – +200%,0 在正中间(ratio = (amount/200 + 1) / 2);数值列加宽以容纳 "-200%"。 - 目标值(识别规则与旧实现完全相同;amount = 幅度/100): * 0 ≤ a ≤ 1:当前值 → 同类极值中较温和的一侧; * 1 < a ≤ 2:该侧 → 另一侧(与旧实现逐位一致); * -1 ≤ a < 0:当前值 → 相邻异类极值中较近的一个(波峰看较高的波谷、波谷看较低的波峰); * -2 ≤ a < -1:较近的异类极值 → 较远的那个(弱峰被压平甚至抹除,左右平滑过渡)。 - 负幅度不得反向增强:目标夹在"不越过当前值"的一侧。平滑偏移的逐点过渡逻辑未改动。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - uitest 443/443:[X] 正幅度 0/50/100/150/200% → 峰 550/555/560/580/600(旧值逐位保持)、 谷 450/445/440/420/400;新增负幅度 -50/-100/-200% → 峰 500/450/400、谷 500/550/560; 超范围夹取、"未反向增强"(极值点)、健康峰不动;滑块中间 = 0%、拖出两端 = ±200%; 构建信号名含「弱峰调整」;[AH] 纯竖直拖拽 16 帧仅 2 次图层重画。 - panperf:拖出网格 900px + 对角平移 31 帧,被拖网格的空白行数与整幅重绘逐帧一致(64 vs 64 → 无空白条带),帧时 10~13ms,末帧差异与水平基线同量级(已知的 ≤0.5px 整数像素残差)。 - layercheck:clobbers=0/24、pan frames wrong=0/4、居中图层 vs 整幅重绘逐像素 0 差异; cursorcheck 30/30;make clean && make all 零警告。 |
||
|
|
da83dc2987 |
修复缩放后平移的绘制错误/抖动与前台卡顿;弱波增强改名弱峰增强并支持 0~200%
渲染(根因与修法)
- 根因:后台图层线程渲染时会临时改写共享渲染状态(m_area/m_plotRect/视界/网格矩形),
与 UI 线程无锁的平移(OnMouseMove)和命中测试相冲 → 后台的"保存→改写→恢复"会把
用户刚做的平移弹回、命中测试读到错位网格;同时 RollWideBuffer 用单一全局锚点给所有
图层算同一位移,而各图层可能是在不同视界下渲染的(后台逐网格)→ 网格之间时间错位、
贴回像素互相矛盾;补细条又误用"当前视界"的映射(与旧像素不同源)→ ≤0.5px 接缝与拖动抖动。
- 修法:
* 删除后台图层线程(m_layerWorker 及任务队列、EnsureLayerWorker、StopLayerWorker、
LayerWorkerLoop、ScheduleStaleLayerRenders、m_layerBack、m_layerAnchorMin/Valid、
m_lastPaintTick、m_layerRescheduleStreak)。图层渲染一律在 UI 线程内同步完成。
* 逐网格独立锚点与滚动:以 LaneLayer::wideMin 为准,补细条沿用该图层自己的映射。
* RenderLaneLayerRegion 新增 wideMin 形参;新增 LayerDefaultWideMin()。
* RollWideBuffer 逐网格:内容指纹不一致或锚点离视界太远 → 该网格整块重画;
否则整数像素自贴图平移 + 只补新暴露的细条。
* Paint 宽基底分支:!LayersReadyForView() 时先同步 RebuildWideBuffer() 再滚动。
* BackgroundLayerRenderCount() 保留为"同步图层区域渲染次数"的诊断计数。
- 效果:平移不再出现网格时间错位、视界被弹回或波形抖动;居中图层与整幅重绘逐像素一致。
弱峰增强(原「弱波增强」)
- 功能改名:界面文案与标识符统一为 WeakPeak/weakPeak(ComputeWeakPeakBoost、
SetLaneWeakPeak*、EdgePanelKind::PEAK、PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS、
PLOT_CMD_LANE_WEAK_PEAK、PeakExtremum 等),导出信号名 "<原信号> (弱峰增强 M% N次迭代)"。
- 等级范围改为 0~200%,目标值分段插值:
* 0~100%:在"目标极值自身的值"与"左右同类极值中较温和的一侧"之间插值
(0% 目标就是当前值 → 完全不动,允许用户做极小幅度调整);
* 100~200%:与原先 0~100% 的插值方式一致(200% 完全拉平到另一侧)。
两段在 100% 处连续。新 1.5 == 旧 0.5,新 2.0 == 旧 1.0。
- Lane::weakPeakLevel 默认 1.5;levelPercent(构建请求/曲线缓存键)范围 0~200、默认 150;
新增 kWeakPeakLevelMax = 2.0 与 kWeakPeakPercentMax = 200;滑块轨道比例 = level / 2。
测试
- 同步更新离屏测试 .preflight/uitest:等级期望改为 0/50/100/150/200 五档;阶数扫描与
"完全恢复"块改用 150%/200% 以保持旧期望;派生信号名校验改用新名;
[J] 改为"补丁帧标记降级 → 渲染到缓存重新有效后用修复帧与整幅重绘比对(0 差异)";
[AF] 面板像素容差 32→96(面板 96% 不透明,底层 ≤0.5px 残留会透出,实测最多 6 像素/72 级);
[X] 预览曲线像素计数的颜色容差 6→40。
- 新增离屏检查 .preflight/layercheck:居中图层与整幅重绘逐像素对比、平移逐网格对齐位移、
视界覆盖检测、跨度抖动下的视界保持检测。
- make release 零警告;uitest 380/380 通过(exit=0)。
|
||
|
|
99247622c1 |
派生曲线计算与 UI 绘制分离(后台线程 + 双缓冲),彻底消除交互卡顿
根因:重计算发生在绘制线程内(Paint 同步算派生预览曲线),一次整段弱波增强约 368ms 会阻塞消息循环,滑块/光标/视界失去跟手。 方案:新增后台计算线程 + 双缓冲。大窗口预览曲线不在绘制线程计算,本帧用最新旧曲线顶替并排后台任务;小窗口仍同步以保证预览实时;后台算完通过自定义消息通知宿主重绘(双缓冲切换)。条目用 shared_ptr 共享、绘制线程钉住整帧有效,后台只追加不改写,无数据竞争。任何改变数据层结构的操作前先 PauseDerivedAsync(join 后台线程),杜绝读取已释放/已移动采样。 实测 VERY_BIG_DATA(5 网格):弱波增强平移 2247ms→15ms,拖滑块 2303ms→15ms,静止重绘→0.25ms,交互均 ≤约 30ms,无 300ms/2s 级尖峰。小窗口同步路径与旧实现逐像素 0 差异。 |
||
|
|
2f7a6bc2d2 |
修复右键网格菜单后悬停提示残留;修复实验性功能预览曲线导致的平移/拖光标卡顿
1) 右键弹出网格菜单前清空绘图区悬停状态(并让 OnMouseLeave 一并清 m_hoverLegend),菜单打开后信号光晕与「可拖拽」提示气泡不再残留。 2) 时间光标处派生取值胶囊原先把请求窗口取成「可见区间 ∪ 光标下标」的并集:光标在视界外时窗口达上千万采样,超缓存上限后每帧重算整段多次迭代滑动平均(实测单帧 300ms+),并随平移/拖滑块/拖光标/改色一起变卡。改为只请求光标所在采样点的小窗口,取值依赖由 CurveSpanFor 保护带覆盖,取值逐位一致、显示效果零变化。 离屏实测 VERY_BIG_DATA:均值滤波平移 326ms→16~23ms,中心轴边沿计数平移 300ms→20ms。 |
||
|
|
b960b03cb5 |
「阶数 N」改称「迭代次数 N」;修复大文件放大后的平移卡顿与越界三角样式
一、命名(用户要求) - 实验性功能里的「阶数 N」全部改为「迭代次数 N」(面板滑块标签、均值滤波/中心轴 的数值框标签、构建提示语)。 - 相关标识符与函数参数统一改名:order → iterations(Lane::MeanFilterItem::iterations、 Lane::weakBoostIterations、ComputeWeakWaveBoost/ComputeMovingAverageIterated 的形参、 MeanFilterCurveFor/WeakBoostCurveFor/WeakBoostValueAt/EdgeCenterCountsFor、 各曲线缓存项的字段、EdgePanelLayout::rowIterations/sliderIterations、 PlotEditTarget::MEAN_FILTER_ITERATIONS/EDGE_CENTER_ITERATIONS、 PlotDragMode::BOOST_ITERATIONS、kWeakBoostIterationsMin/Max/Label、 DerivedSignalRequest::iterations 等)。网格拖拽重排里的局部 order 为避免歧义 改名为 laneOrder;`std::memory_order_*`、`rowOrder`→`rowIterations` 一并核对无误。 - 导出信号尾缀统一为「N次迭代」: `<原信号> (N点均值 M次迭代)`、`<原信号> (弱波增强 X% M次迭代)`; 弱波增强此前缺失该信息,现已补上(两种派生信号都恒定带迭代次数)。 状态提示语同步为「… M 次迭代信号。」。 二、越界三角与光标读数的样式统一(用户要求) - 新增 `kMarkerTriangleGap`:网格内数据点越界时,方向三角与数值标注之间**留出间隙** (此前三角与胶囊贴在一起,看起来像胶囊上多出的尖角);中心轴/均值滤波/弱波增强的 派生取值标注同样处理。 - 光标时间读数(以及双光标 Δt 读数)改用与网格内数据点**完全一致**的越界样式: 三角画在胶囊**外侧**、尖端朝外、与胶囊之间留出同样的间隙;为此把读数胶囊在越界一侧 向内让出「三角深度 + 间隙」。旧的"三角压在胶囊上沿"的画法以及为它额外留出的叠放 间距(kCursorTipStackGap)一并删除。 三、大文件放大后"平移 / 改配色非常卡"的根因与修复(用户要求,流畅性优先) 用离屏探针(`.preflight/perf3`)在 VERY_BIG_DATA(1.77GB、2580 万采样 × 5 信号、 1224×796 绘图区)上先量化,实测到的三个根因: 1. **包络带"梳状化"**:当信号的振荡周期与像素列宽度相当时(编码器方波在 2ms/大格下 约 3 个采样点一列),逐列包络会在"全高"与"极窄"之间交替——实测 1224 列里有 988 处 全高跳变。这种多边形让 GDI+ 的扫描线填充耗时暴涨:同一条 2448 顶点的路径 **27.6ms → 1.1ms(±1 列极值扩展后快 25 倍)**。这正是"某些放大等级下平移特别卡" (实测 2ms/大格平移 87ms,其余等级 17~20ms)的原因。 → `EmitEnvelopeBand` 在拼装包络带时对列窗口做 ±1 列的极值扩展(纵向最多各多 1 像素, 肉眼不可辨),一次性消除该病理。 2. **派生预览曲线按折线逐段描边**:均值滤波/弱波增强曲线在可见采样数超过像素列数时 仍按"每 2 像素一个顶点"的抗锯齿折线绘制,而这类曲线常叠在方波上(大量近垂直跳变), 实测单条 12~42ms,5 个网格合计上百毫秒。 → 稠密时改用**逐像素列的包络带**(复用 `EmitEnvelopeBand`)一次 FillPath,实测 0.56ms / 1.53ms(分别快 22 倍 / 28 倍),视觉与细折线一致(列宽就是 1 像素)。 3. **悬停光晕用宽笔描整条折线**:实测悬停一帧多花 35ms。 → 包络带模式下改为"把带整体外扩后填充一次"(`PaintBandHalo`),悬停帧恢复正常。 另外修掉两个相关的固定开销: - `UiMeasureTextWidth` 增加 (字体, 文本) 记忆化缓存:它是布局代码的热点(浮动面板几何 按「网格数 × 面板种类」重复计算,每帧重复测量同一批字符串成百上千次),实测单个面板 的绘制因此从 3.2ms 降到 1ms 量级。字体重建(DPI 变化)时由 `UiClearMeasureCache` 作废。 实测(VERY_BIG_DATA 全选 5 信号,绘图区全重绘 = 平移一帧): - 20ms/大格(用户报告的场景):平移 20.1ms → **11.3ms**; - 2ms/大格(此前最糟):平移 86.7ms → **12.3ms**; - 网格 1 开弱波增强:63.9ms → **17.6ms**;5 个网格全开:136.6ms → **31.9ms**; - 悬停信号:整帧 49.6ms → **15.5ms**(与不悬停持平,光晕几乎免费)。 仍存的已知热点:0.5ms/大格附近(可见约 1000 个采样点、走**逐点描边**路径)平移约 28~39ms —— 该区间采样比像素还稀疏、无法用包络带替代,且用户明确要求保留抗锯齿, 本次未动。 四、验证 - 离屏 harness(窗口 SW_HIDE、不占用鼠标/键盘/屏幕)**355 项检查全部通过**, 新增 `[AD]`:越界读数胶囊向内让位、三角在外侧、三角与胶囊间有真实间隙、 胶囊上方不再有内容;取值标注与三角之间保留 `kMarkerTriangleGap` 的间隙(用 散点模式排除波形干扰后逐像素断言)。`[X]/[Y]/[AA]/[AB]/[Z]` 等既有用例同步通过。 - 性能结论来自 `.preflight/perf3`(离屏探针 + 两套微基准),已随任务清理。 |
||
|
|
8c8508e5f8 |
弱波增强:去掉前置滤波并新增「阶数 N」滑块;修复选区放大越界绘制与浮动面板 ✕ 交互
- 弱波增强不再做任何前置滤波(删除内部的 5 点滑动平均 BoostShapeAt): 极值点直接在输入采样上识别。需要平滑时用户可以先用「均值滤波」构建一条 滤波后的信号,再对它做弱波增强。 - 新增「阶数 N」参数(1~10,滑块输入),语义与均值滤波的阶数 N 一致 —— 把同一处理重复应用 N 次,每阶以上一阶为输入: 一阶时改定量严格等于「等级」对应的目标值;多阶会让相邻弱波互相抬高彼此的 基准,从而把较宽的弱波区逐步恢复(实测三连弱波的中间峰 550→575→598.4)。 某一阶若不再产生改动会提前结束迭代。 `ComputeWeakWaveBoost(values, level, order, out)`; `Lane::weakBoostOrder`(默认 1)随 LaneCarry / SplitLane / 引用重映射同步, 并进入 SceneContentHash;曲线缓存键变为「信号 + 等级 + 阶数」。 - 浮动面板同步新增一行「阶数 N」滑块(新拖拽模式 BOOST_ORDER、 EdgePanelLayout::sliderOrder);「构建为新信号」命名带上阶数 (形如 `<原信号> (弱波增强 60%×2阶)`)。 - 修复:选区放大时鼠标被捕获到绘图区之外(工具栏 / 信号面板 / 时间轴)会把 选区块画到绘图区之外,而拖拽期间只失效绘图区 → 留下无法擦除的墨迹。 现在拖拽时把选区夹在绘图区内,PaintRubberBand 再对 m_plotRect 求交一次。 - 修复:浮动面板的 ✕ 位于标题栏矩形内部,按下后会继续落到「标题栏 → 拖动面板」 分支,表现为「按住 ✕ 却把面板拖走了」;现在按下 ✕ 直接消费该次点击(抬起时 仍按原语义确认关闭),并把 ✕ 的光标改为手型,与其它关闭按钮一致。 - 修复:✕ / 滑块 / 底部按钮的悬停高亮依赖鼠标坐标,但从同一网格的波形移到面板上 时悬停状态毫无变化 → OnMouseMove 返回 false → 宿主不重绘 → 高亮不出现。 现在「进入 / 离开 / 在面板内移动」都会请求重绘。 - 离屏验证(窗口隐藏,不占用鼠标/键盘/屏幕):345 项检查全部通过,含 阶数扫描(N=1 只改外侧弱峰、N=2 传导到中间弱峰、高 N 逼近健康幅值且不越界、 健康峰在任何阶数下逐位不变)、两个滑块的按压/拖拽/钳制、选区越界零墨迹 (绘图区外像素差 0)、面板 ✕ 悬停高亮与按下不拖动、光标拖拽始终在视界内, 以及真实 20K 电机纹波录波(信号范围保持不变、20%+ 采样被调整)。 |
||
|
|
67f659ad23 |
实验性功能:移除「差异放大」、新增「弱波增强」;修复放大后拖拽光标越出视界
- 移除差异放大:ComputeDiffAmplify、EdgePanelKind::DIFF_AMP 面板、DIFF_AMP_*
编辑目标、菜单项与相关缓存,并删除仅供它使用的 SlidingMax / SlidingExtrema /
float 版滑动平均。
- 新增弱波增强 ComputeWeakWaveBoost(values, level, out)(O(n)):
1) 对轻度平滑(5 点滑动平均)后的信号扫描单调段,方向翻转处即为波峰/波谷;
2) 波峰低于左右两个波峰均值(波谷高于左右两个波谷均值)即判为弱波;
3) 波峰目标 = 较小波峰 + 等级×(较大−较小);
波谷目标 = 较大波谷 − 等级×(较大−较小);
4) 极值点取目标值,两侧数据点在相邻极值点的偏移量之间用平滑步进过渡,
因此健康周期逐位不变、数据端点原样保留。
- 弱波增强面板:逐信号复选框 + 「弱波增强等级」滑块(0–100%,整个网格共用)
+ 「构建为新信号」按钮;构建名形如「<原信号> (弱波增强 M%)」。
等级进 SceneContentHash,改动即触发重绘;Lane 新增
weakBoost/weakBoostLevel/weakBoostItems,并在 LaneCarry、SplitLane、
RemapAfterSignalInsert/Removal 中同步。
- 修复:放大到一定程度后左右拖拽时间光标(尤其抓取较宽的读数标签、抓取偏移较大时)
会把光标时间推到视界之外;现在 CURSOR_MOVE 与 CURSOR_SPAN_MOVE 结束后
按可见区间夹取(视图变化仍不移动光标,保留贴边读数与方向三角)。
- 离屏验证(窗口隐藏,不占用鼠标/键盘/屏幕):295 项检查全部通过,
含等级 0/50/100% 的目标值与健康区逐位不变、滑块拖拽与左右钳制、
合成纹波端到端恢复(弱周期峰峰值 100→160,健康周期 200 不变)、
光标拖拽始终落在可见区间内。
|
||
|
|
d4206a8d67 |
实验性功能新增「差异放大」,并修正「构建为新信号」按钮的间距
1. 新增「实验性功能 → 差异放大」(与其它实验功能一样是带勾选的开关)
- 用途:纹波类信号某个周期(甚至半个周期)的 M 形下凹明显偏弱时,
把这段下凹按比例拉回周边振幅,恢复正常周期的对比度,便于看出波形异常。
- 算法(全 O(n),不额外占用与信号长度成正比以外的内存):
以半径约半个纹波周期的滑动平均作为纹波中心线,用邻域内「交流分量的最大值」
作为局部纹波振幅,取全信号振幅的 75 分位作为目标振幅,
逐点增益 = 目标振幅 / 局部振幅(夹在 1 到放大倍数之间),
输出 = 中心线 + 增益 × 交流分量,再夹在周边包络内。
- 关键性质(均已用合成正弦逐点验证):
* 增益下界恒为 1,因此**只有被判定为"偏弱"的采样点会改变**,
其余采样点输出与原值逐位相同 —— 实测逐周期表格中只有一个周期发生变化;
* 放大结果夹在周边包络内,因此**任何一点都不超出原有的振幅范围**,
整体值域与其它周期的峰峰值都不变;
* 端点自动收敛到不放大,避免记录首尾被整片抬升。
- 参数「窗口点数 X」(窗口长度,建议略大于纹波周期的一半)与
「放大倍数 N」(增益上限)都可在浮动面板内按信号编辑。
2. 浮动面板
- 与「均值滤波」完全同一套排版:自上而下逐信号条目,
第一排信号名与复选框、第二排两个参数输入框;取消勾选即不显示该信号的参数与预览曲线;
启用时默认只勾选网格内第一个可见信号。
- 新增「构建为新信号」按钮,按各信号自己的参数把结果插入到源信号下方,
与「均值滤波」的构建机制完全一致(统一走同一套请求与插入流程)。
3. 光标交互
- 纵向光标额外标注差异放大曲线在该时刻的取值,配色与原始信号一致,
数值尾部括号内容为「差异化」,双光标时同样带增量。
4. 修复「构建为新信号」按钮与面板下边界的间距
- 原按钮行直接贴着面板下边框,与面板左右内边距及内容间距都不协调;
现在按钮上方与下方各保留一个与左右一致的内边距(均值滤波与差异放大同时生效)。
测试:离屏验证套件新增两组检查(合成正弦上算法只改动一个周期、放大不越界、
参数上限生效、面板排版与按钮间距;再写一份合成纹波文件真正加载渲染并核对曲线数值),
合计 289 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
|
||
|
|
8801c8f019 |
修复缩放视界下拖拽与改色卡顿、加载字节进度、光标越界三角与中心轴均值滤波统一
1. 修复选区放大后的拖拽/改色卡顿(先实测再修,不猜)
- 用离屏探针量化:缩放视界下一帧要画约 250 条网格线,GDI+ DrawLine 每条 0.1~0.19ms,
整帧 26.4ms;而包络带填充本身只要 0.36ms,真正的瓶颈是逐条直线调用。
- 网格线、时间轴刻度、绘图区边框、网格分隔线全部改为 GDI 批量 1px 直线绘制
(一次 SaveDC/IntersectClipRect/创建画笔,逐段 MoveToEx/LineTo);
纵轴数值胶囊改为把所有圆角矩形合并成一个路径、只调用一次填充。
- 半透明网格线颜色先用底色预混成不透明色,否则 GDI 无法绘制透明度;
GDI 绘制不受 GDI+ 裁剪约束,批量绘制一律显式设置裁剪矩形。
- 实测:缩放视界下场景缓存失效帧 26.4 -> 8.25ms,改色应用 7.1 -> 3.15ms,
缓存命中帧 2.06 -> 0.30ms。
2. 读取文件时显示"已读取 / 总大小 百分比"
- 解析进度回调扩展为携带真实字节数(已读字节、文件总字节);
列重组等没有字节可计的阶段只显示百分比。
- 新增字节数格式化:单位在 GB / MB / KB 之间自适应,只输出数字与单位;
已读大小随进度自动切换单位。
- 进度浮层按"已读取 / 文件总大小 百分比"显示,两个大小与百分比都保留两位小数,
完全取代原来的整数百分比。
3. 网格编号拖拽重排的绘制修复
- GX 编号悬停时保持普通箭头光标(不再变成四向箭头),按下拖拽时才使用上下箭头。
- 拖拽时先把整幅静态场景(含主时间轴)贴回,再把目标槽位高亮、被拖网格的轮廓与
投影裁剪在绘图区内,因此拖到工具栏甚至更上方、或拖到时间轴及下方都不再出现
残留与错乱。
4. 网格 Y 轴数值只在完全移出可视区时才不绘制
- 原来只要碰到边界就整条不画;现在按可见范围裁剪胶囊与数值,
部分可见时绘制未被遮挡的部分。
5. 边沿计数横向光标的读数与越界指示
- 幅值读数右移,让开横线左端的三角把手,不再遮挡。
- 幅值超出视界时(横线贴边显示)在读数上方/下方绘制方向三角,
并且与读数水平居中对齐。
6. 常规光标读数越界与双光标叠放
- 光标滞留在视界之外时,时间读数同样绘制方向三角。
- 双光标读数重叠时按时间值排序:小值在上、大值在下(原来固定把第二个上移,
左右光标交换后会显示成大的在上)。
- 双光标同时越界到同一侧时,两条时间读数与时间差值读数在同一列垂直对齐,
并为差值读数绘制方向三角;叠放间距按三角深度放宽,三角不会再压在相邻读数上。
7. 中心轴边沿计数与均值滤波统一
- 参数由单一的"平滑点数 N"改为按信号的"均值点数 X + 阶数 N",与实验性功能中的
均值滤波完全同构(同样的命名、同样的作用、同样的曲线实现),
两条曲线逐位一致,光标处的取值标注规则也统一。
- 浮动面板改为自上而下的逐信号条目,每个条目三排:
第一排信号名与复选框,第二排均值点数与阶数输入框,第三排该信号的上升沿/下降沿统计;
取消勾选即不显示该信号的参数编辑与统计结果,启用时默认只勾选第一个可见信号。
- 网格重建继承、拆分网格、信号插入/删除的下标修正与场景缓存指纹都同步覆盖新条目。
测试:离屏自动验证套件新增一组检查(字节数格式化、解析进度字节、编号悬停/拖拽光标、
双光标读数排序与对齐、中心轴三排布局与参数、横向光标越界三角、加载浮层渲染等),
合计 241 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
|
||
|
|
bc8db72f18 |
修复色域拖拽回弹、网格 GX 拖拽重排、均值滤波多阶与逐信号条目
1. 自定义颜色面板:拖动时鼠标移出控件不再"弹回去"
- 根因是选点位置每帧都从 RGB 反推:色相 1 与 0 是同一个颜色(红),拖到色相条底部
下一帧反推回 0,指示条弹到顶部;拖到色域方块右下角之外时明度为 0(黑色),
黑色的饱和度无定义,反推得 0,选点弹到最左边。
- 现在把"选点位置(饱和度/明度)与色相"作为独立状态保存,只由对应控件与外部改色维护;
越界一律夹取到最近边界,因此鼠标离开控件后选点停在边界上。
- 统一色域方块、色相条、预览色块与色号框的圆角;RGB 滑块适当加粗;
色域选点去掉外圈深色描边(只保留白色描边),色相指示器改为白色描边的圆角长方条、
底色取该位置的颜色;色相条改用分段线性渐变绘制。
2. 网格 GX 编号支持拖拽重排
- 按住网格图例左侧的 GX 徽标即可上下拖动,整块网格(连同波形与标尺)跟随鼠标移动;
途经的网格保持自身大小、整体让出一格,目标槽位有吸附高亮提示。
- 松开后真正重排并从下到上/从上到下重新编号,信号列表徽标、图例徽标、菜单与浮动面板
中所有 GX 编号同步更新;手动排布后切换为自定义划分,后续重建网格不会打乱次序。
- 拖动期间复用静态场景缓存,按网格条带平移贴图,因此大文件下依然流畅。
3. 均值滤波升级为"均值点数 X + 阶数 N"
- 原"均值点数 N"更名"均值点数 X",新增"阶数 N":预览曲线以 X 点均值滤波的结果为蓝本,
再重复同样的滤波共 N 阶(1 阶即与原来的单次滤波逐位一致)。
- 浮动面板改为逐信号条目:第一排是信号名与复选框,第二排是均值点数与阶数输入;
只有勾选的信号才绘制预览曲线,未勾选时不显示第二排;启用功能时默认只勾选第一个信号。
- 参数按信号保存(同一网格内不同信号可用不同参数),「构建为新信号」按各自参数构建。
|
||
|
|
40b12d6e8b |
调色性能优化 + 新增色域方块拾色 + 取值胶囊高对比配色 + 数据点标记修复
1. 修复自定义颜色滑动卡顿(VERY_BIG_DATA 全信号可见时 36ms/步 → 约 12ms/步)
- 根因:场景缓存的内容指纹把逐信号的颜色/不透明度/线宽/模式都算了进去,
改一次颜色就整幅失效,于是所有网格的波形都要重新光栅化。
- 新增"不含外观样式"的几何指纹与样式快照:只有在网格结构、视图几何、信号集合与
文字都不变、仅外观样式变化时,走"局部重画"快路径 —— 只重画颜色变了的那个网格,
再把结果写回场景缓存(下一帧回到纯贴图)。
- 补丁矩形向内对齐到整数像素,避免把属于相邻网格的一行擦掉而留下 1px 接缝;
底色填充按补丁矩形本身进行(大面积不透明填充会走 GDI 快路径,不受 GDI+ 裁剪约束)。
- 该优化对调色板点击、线宽/不透明度滑块、绘制模式切换同样生效。
2. 自定义颜色新增"方块色域图"拾色
- 新增饱和度-明度方块(横轴饱和度、纵轴明度)与纵向色相条,可直接在色域图上取色。
- 与面板内其它控件完全联动:RGB 滑块、十六进制输入、预览色块同源(RGB 为唯一数据源),
并记忆色相,消色差时仍可用色相条换回颜色。
- 色域方块按当前色相预渲染缓存,色相不变时不重复绘制;色域内用十字光标、色相条用纵向光标。
3. 取值胶囊的对比度描边与文本色改为对信号配色线性响应
- 原实现只有一个亮度阈值,文字/描边只有黑、白两套固定取值。现按底色亮度选定亮/暗一侧,
再与底色的互补色按固定比例线性混合(互补色亮度恰为 1−底色亮度),
因此文字与描边的亮度随信号配色线性变化,且任意底色下都保持高对比。
4. 修复光标数据点越界三角的判定与绘制
- 是否改用三角,以"数据点本身是否超出绘制边界"为准,不再因为数值标注被贴边而改变形状。
- 三角方向修正为尖端朝外(此前上越界画下三角、左越界画右三角,方向完全相反),
内三角改按质心收缩,描边不再是歪的;上下越界时三角与显示的数值纵向居中对齐。
|
||
|
|
d3c83237f1 |
信号 X 按钮移至行尾并统一抬起响应,光标读数补秒单位,图例悬停高亮信号,去除筛选框内圈
1. 信号条目的「移除」X 按钮移到最右端
- 与分组行关闭 X 同尺寸(18)、同右边距(同一列)、同行内垂直居中,描边宽度也统一。
- 网格编号 GX 徽标让位到 X 左侧,二者恒在列表最右侧;名称可用宽度改按实际徽标是否显示计算,
不再白留一段空间。
2. 所有 X 关闭按钮由"按下即响应"改为"左键抬起才响应"
- 与常见的可取消按钮一致:按下只记录目标,按住后移开鼠标再松开视为放弃。
- 覆盖文件分组 X、信号 X、筛选框清空 X、标签页 X 与实验性功能面板 X。
- 筛选框的清空 X 在编辑态下走的是宿主的 EDIT_FIELD 分支(OnEditMouseDown/OnEditMouseUp),
非编辑态才走面板的按下/抬起分支,两条路径都已改造。
- 标签页 X 原先只要求"抬起时仍在同一标签内",现收紧为"仍在 X 上"。
- PlotView::OnLButtonUp 增加横坐标参数以便判定抬起位置。
3. 时间光标读数补上 s 后缀
- 新增 FormatCursorTime,标签宽度测量与文字绘制同时使用,避免文字被裁掉;
内联编辑仍使用不带单位的秒值。
4. 鼠标悬停网格图例时高亮对应信号
- 除图例条目的半高亮外,对命中信号绘制与"鼠标贴近曲线"相同的光晕,
便于确认该条目对应哪条曲线。
5. 筛选信号输入框编辑态不再出现内圈
- 把输入框绘制拆分为"底板+边框"与"仅内容"两部分;筛选框外框已自绘,
编辑态改用仅内容版本,空内容时不再套一层多余的圆角内框;
有输入时的清空 X 不受影响。
|
||
|
|
a1e30985b8 |
修复输入框全选高亮溢出、标签页重命名无法失焦、新增信号行删除按钮
1. 内联输入框全选高亮越界(组名/标签页名/信号名编辑)
UiFillRect 在不透明且面积 >= 20000 时走 GDI FillRect 快路径,而 GDI 填充不受
GDI+ SetClip 约束;长文本全选时高亮宽度可达上千像素,于是画到输入框之外。
UiDrawEditField 现在先把高亮矩形与裁剪区求交再填充。
2. 标签页重命名处于编辑态时,点击输入框之外的任何位置都不退出编辑
Application 只在点击落在标签栏内时才把点击交给 TabBar。现在 WM_LBUTTONDOWN
会先检查 m_tabBar.IsEditing(),是则提交(OnEditKey + OnTabBarResult)并退出编辑态,
覆盖绘图区、信号面板、工具栏、分隔条等位置,且不会丢失新名字。
3. 信号选择区域每个信号新增 ✕ 删除按钮
- 位置与所在组复选框同一列(垂直对齐),悬停显示手型光标并高亮。
- 点击后面板向宿主发送 WM_APP+2;Application::RemoveSignal 会重映射所有标签页的
可见性与网格引用、修正当前选中项,再真正从 DataSet 中删除该信号并释放采样内存;
删除组内最后一个信号时退化为移除整个文件分组。
- DataSet::RemoveSignal 采用移动填充 + shrink_to_fit,保证缓冲区内即刻析构。
|
||
|
|
350538c634 |
命名审查与垃圾代码清理(纯重命名 / 死代码删除,无行为变更)
前提:加载完成后全部采样都已驻留内存,程序此后与源文件再无联系。 据此修正了一批会让人误以为"仍在读写文件"的命名: - SampleFileAt → SampleSignalAt(取的是内存信号在给定时刻的采样值)。 - DataSet::AddPreparedFile → AddDataFile。 - PlotView::InvalidatePolylineCache → InvalidateDerivedCaches (它同时清空折线、边沿统计、中心平均值曲线、静态场景缓存与悬停引用)。 - PlotView::CursorColor(int which) → CursorColor()(两条光标同色,参数从未使用)。 - UiEllipsize(g,…) → UiEllipsize(…):纯测量不绘制,删掉被忽略的绘图上下文参数; 同时删除与 UiMeasureTextWidth 完全重复的 UiTextWidth,调用点统一到 UiMeasureTextWidth。 清理残留参数((void)xxx;): - TabConfig 的可见性/网格族(SetVisible / ToggleVisible / SetFileVisible / SetAllVisible / SoloSignal / SetMode / SyncLanes / FileHasVisible / FileAllVisible)去掉从未使用的 const DataSet& 参数(仅 SyncToDataSet 真正需要数据层,保留)。 - PlotView::OnLButtonUp(int y)、OnMouseWheel(int delta, bool shift);PainterSignalHalo 去掉未用的 ref。 - BuildDataFile 中未使用的局部 rowCount。 删除无人调用的死函数: DataSet::Clear / AddFromTable;SignalColorCount;WideToUtf8 / StartsWith; PlotView::ViewMinTime / ViewMaxTime / EdgePanelRect / EdgePanelCloseRect / EdgeCenterPanelRect / ScaleLaneRange / BeginLaneValueEdit / LaneContentHeight; SignalPanel::NeedsScrollBar / IsFilterEditing / FilterText / ContentHeight; PopupMenu::EditingItemId;TabBar::IsDragging;TextEdit::PlaceCaret。 删除只写不读的死字段:CsvTable::sourceRowCount(与 rowCount 等义)与 skippedRowCount。 以 VERY_BIG_DATA.CSV 回归验证:加载与勾选、筛选框(含"无匹配的筛选信号")、 样式区线宽文案省略、选区放大(横向与纵向)、双光标读数与 Δt、右键菜单(Y 轴数值与 实验性功能子菜单)、合并/拆分网格、标签与信号重命名省略号,行为均与改动前一致; make clean && make all 无任何警告。 |
||
|
|
f5028b1e18 |
实验性功能新增「均值滤波」+ 构建为新信号
- 「实验性功能」二级菜单新增带勾选的「均值滤波」:开启后为本网格每条信号叠加绘制
一条 N 点滑动平均曲线,算法与「边沿计数(中心轴)」的中心平均值曲线**完全一致**
(同一实现 ComputeMovingAverage),配色同样由信号色派生为高对比度颜色、透明度一致。
- 新增均值滤波浮动面板:标题栏可拖拽、✕ 关闭,面板内列出被处理的信号名、
「均值点数 N」内联编辑行,以及底部的「构建为新信号」按钮(悬停高亮 + 手型光标)。
- 「构建为新信号」:把滤波后的曲线作为新信号插入,命名为「<原信号名称> (N点均值)」,
归属到原信号所属组的原信号位置**下方**,成为独立的新信号;其所在网格也移动到
原信号网格的下方。合并网格时一次按钮为该网格内每条可见信号各构建一条。
为此新增 DataSet::InsertDerivedSignal / TabConfig::RemapAfterSignalInsert /
TabConfig::MoveLaneOfSignalBelow / TabConfig::RenameLanes(插入位置之后的信号引用
与可见性列同步重映射)。
- 光标交互与「边沿计数(中心轴)」同构:光标处标注均值滤波曲线的取值
与双光标增量,文案为「值(均值滤波)」/「值(+增量 均值滤波)」;
两条派生曲线的取值胶囊已抽成同一段绘制逻辑。
- 浮动面板种类由 bool centerAxis 改为 EdgePanelKind{COUNT,CENTER,MEAN},
布局/宽度/命中测试/绘制统一走同一套几何。
- 以 VERY_BIG_DATA.CSV 验证:曲线预览、N 编辑、双光标取值标注、
单信号与合并网格两种情况下的新信号命名与插入位置均正确。
|
||
|
|
8ae8d7af4f |
中心轴曲线配色 + 筛选框交互完善 + 双光标拖拽性能修复
- 中心轴边沿计数的平均值曲线改为「由信号色派生的高对比度颜色」:保持色相、 大幅拉开明度(与原始波形高对比),透明度与原信号一致;原信号改色时曲线 颜色随之变化,而光标显示该曲线数值时仍沿用原始信号的配色样式。 - 信号筛选框:内容区左右内缩改为与上下一致(不再「左右空隙明显更大」); 有输入时右侧显示清空 ✕(悬停高亮 + 手型光标,点击清空且保持编辑态); 筛选无结果时在列表区提示「无匹配的筛选信号」。 - 光标数值超出上下视界时,三角箭头标识下移 kCursorHandleReserve, 不再遮挡光标自身的三角把手(含双光标模式)。 - 性能:新增「装饰区域脏标记 + 绘图区局部回送」快路径——绘图区内的悬停/拖拽 期间跳过标签栏/工具栏/面板/分隔条的重绘,且只 BitBlt 绘图区矩形; WM_PAINT 依据更新区域是否落在绘图区内决定快慢路径,窗口缩放/遮挡露出/ 菜单与拾色器弹出等场景仍整帧重绘。 以 VERY_BIG_DATA.CSV 全信号双光标拖拽实测:单帧耗时由约 28ms 降至约 12ms。 同时修复拖拽期间残留的信号悬停提示气泡。 |
||
|
|
fe72afc83c |
绘图交互完善 + 稠密区域拖拽性能修复(VERY_BIG_DATA 2ms/大格不再卡顿)
一、功能修正 - 修复「光标数据点在上下视界外不绘制」的问题,并让上下越界与左右越界一样用三角表示: 新增 `DataMarker` / `PaintDataMarker()`,统一处理「可视区内画圆点、越界则钉在网格边上 画三角指示方向」的四向情形(左/右/上/下)。光标取值与中心轴取值都改走这一套, 因此纵向被裁掉的数据点不再消失。 - 「边沿计数(中心轴)」的中心平均值曲线改为**跟随原信号的样式**: 与原始信号同色但更透明,线宽同样跟随(lineWidth + 0.6,下限 1.4), 原信号改颜色/线宽/透明度后曲线同步变化(曲线参与场景指纹,样式变化即失效重绘)。 光标显示中心值时的胶囊也改用**原始信号颜色**(一一对应),并**始终按线段模式**着色, 不再使用光标色。 - 「拆分当前网格」在可点击时追加尾缀「(N个信号)」(N = 该网格内信号数), 只有 1 条信号(不可点击)时不加尾缀。 二、新增:左侧信号筛选框 - 信号列表上方新增「筛选信号」输入框:空内容时显示灰色占位提示,单击进入编辑, 点击别处或回车结束编辑;列表内容随输入**实时**变化。 - 支持 `*` 通配符(`*` 等价于"任意字符",因此 `*` 单独出现等价于不过滤),忽略大小写。 - 筛选结果**仍以组为单位**,组默认展开,只显示匹配的信号;组内无匹配信号时整组不显示。 - 复用统一的 `TextEdit` 编辑模型,因此光标/选区/Ctrl+A/C/X/V 等行为与其它输入框一致。 三、性能:修复 VERY_BIG_DATA.CSV 在 2ms/大格密集区域拖拽卡顿 以「显示全部 5 条信号 + 时间轴缩放到 2ms 一大格」为场景逐段打点定位后修复: - **主因:稠密包络的锯齿描边光栅化**。抽稀时每列产生 min/max 两个顶点交替的锯齿, 2000+ 条近乎满幅的斜线段在 GDI+ 抗锯齿下要光栅化几十万像素(实测每网格 14ms, 5 网格约 70ms/帧)。 改为**包络带一次填充**:逐列记录上/下包络,收段时拼成「上包络正向 + 下包络反向」的 闭合多边形,用一条 `GraphicsPath` + `FillPath` 绘制(显式 `FillModeWinding`, 自交包络不会挖洞)。基准测试(1224 列):14.26ms → **1.69ms(约 8.4 倍)**, 且**保留抗锯齿**、静态帧与拖拽帧完全一致(不是"交互降级")。 逐像素对比确认观感等价(墨量覆盖率 90.8% vs 91.3%)。 包络带同时用于静态绘制与 MOVE_SIGNAL 的拖拽虚影。 - **大面积不透明填充改走 GDI**:`UiFillRect` 在面积 ≥ 20000px² 且颜色不透明时使用 `GetHDC + FillRect`(GDI+ FillRectangle 约 2.2ns/像素,整窗背景 1.7M 像素要 0.9ms、 面板背景 1.9ms)。半透明填充仍走 GDI+(GDI 不做 alpha 混色);小面积仍走 GDI+ (避免 GetHDC 固定开销)。顺序安全:GetHDC 会先刷入已排队的 GDI+ 内容。 - **`UiTextWidth` 不再触发 GDI+ 批次刷新**:改为复用 `UiMeasureTextWidth` (`GetDC(nullptr)`),`UiEllipsize` 逐字符回退时的十几次刷新被完全消除。 实测(125% 缩放、1725x996、5 信号、2ms 一大格): 绘图区绘制 47~87ms → **3.6~4.5ms**;整帧最大 196.8ms → **约 17~21ms(≈50~60fps)**, 与空闲帧基本持平,即"拖拽与不拖拽一样流畅"。 验证(VERY_BIG_DATA.CSV;面板行坐标因新增筛选框整体下移,复选框客户区 (61,250) 起、行距 33): - 筛选框:输入 `B` 后只剩「编码器B相」「电流钳采集(ABSx10)」,组仍展开;清空即恢复。 - 拆分:合并 3 条信号后右键显示「拆分当前网格(3个信号)」;单信号网格该项置灰无尾缀。 - 光标/中心轴:竖线光标取值与中心值胶囊均按四向越界正确显示三角/圆点。 - 稠密区域:5 信号全选 + 缩放到 2ms 一大格,左键拖拽信号、左键平移、中键平移均流畅; 渲染观感与修改前等价(叠图对比)。 - make clean && make all 无警告无错误。 |
||
|
|
82a29362eb |
网格管理增强:新信号置底、拆分当前网格、中键自由平移、选区模式图例可用、面板悬停高亮、中心轴样式与越界三角
- 新勾选的信号所在网格固定出现在**最下方**:
原先 PER_SIGNAL 模式按 visibleRefs(文件/列序)重建网格,新勾选的信号会按其列序插到
中间,位置看起来"不确定"。现在先按「原有网格位置」排序(已分配过网格的信号保持相对次序),
再把尚未分配网格(新勾选)的信号统一追加到末尾。
- 网格右键菜单在「实验性功能」上方新增「拆分当前网格」(`PLOT_CMD_LANE_SPLIT`):
把该网格内的信号按个拆分成独立网格,新网格插入到原网格的**下方依次排列**,
原网格下方的网格顺次后移。网格编号(GX)完全由位置推导,因此左侧列表徽标、图例徽标、
面板标题、菜单标题等所有使用该编号的地方会同步更新,不会错位。
新增 `TabConfig::SplitLane()` / `PlotView::SplitLane()` / `PlotView::LaneSignalCount()`
(仅 1 条信号时菜单项置灰)。拆分出来的网格继承原网格的网格自身状态
(Y 轴自适应、边沿计数、面板位置等)。
- 鼠标**中键**按下拖拽也可自由查看区域(水平平移时间轴 + 纵向偏移当前网格幅值):
新增 `PlotView::OnMButtonDown/OnMButtonUp` 与 `Application::WM_MBUTTONDOWN/UP`,
**任何缩放模式下都可用**(不改变当前模式)。拖动期间 `WM_MOUSEMOVE` 的按键掩码
加入 `MK_MBUTTON`,光标刷新为抓握图标。
- 选区放大模式下不再阻断图例交互:把「点击图例条目 → 选中信号」的判定移到选区放大分支
**之前**,并让图例悬停(半高亮)在选区放大模式下同样生效(该模式只应改变拖拽语义)。
同时选区放大模式下也可用中键拖拽平移。
- 实验性功能浮动面板悬停时高亮其所属网格(与鼠标直接停在网格上一致):
`OnMouseMove` 中若鼠标落在面板上则把 `m_hoverLane` 置为该面板所属网格。
- 「边沿计数(中心轴)」:
* 中心平均值曲线的颜色样式改为与光标一致(`CursorColor(1)`,即「边沿计数」横向光标
与中心值胶囊的颜色),曲线与其取值标注、光标形成统一的视觉语言。
* 中心值标注形式改为「数值(中心轴)」(数值在前、尾缀在后);
双光标模式下由右侧光标额外显示增量,形如「数值(+0.0008 中心轴)」,
与常规信号的增量显示保持一致(无尖头括号)。
* 修复:光标处于当前显示区域之外(贴边显示)时,常规数据点会变成三角指示方向,
而中心轴数据点仍是圆点。现在中心轴数据点复用同一分支,越界时同样绘制三角。
验证(TestData/VERY_BIG_DATA.CSV,125%、1725x996):
- 置底:先勾选第 5 个信号(电流钳采集(原始))→ G1,再勾选第 1 个信号(编码器A相)→ G2,
新勾选的信号确实出现在最下方。
- 拆分:G1(编码器A相) / G2(飞线IO+编码器B相) / G3(电流钳(原始)) 时拆分 G2,
得到 G1 / G2(飞线IO) / G3(编码器B相) / G4(电流钳(原始)),
原本在中间的电流钳顺延为 G4,左侧徽标、图例徽标全部同步。
- 中键:常规模式与选区放大模式下中键拖拽均能把时间轴平移到 80~160(截图核对)。
- 选区放大模式:悬停图例条目显示半高亮;点击图例条目可切换选中信号(左侧高亮随之切换)。
- 面板悬停:鼠标停在 G1 的「边沿计数」面板上时 G1 区域被高亮(与悬停 G1、G2 两种情形
做区域哈希对比,确认高亮归属正确)。
- 中心轴:曲线为光标橙 `#CB4B16`;面板标题「中心轴边沿计数」、底部「平滑点数 N = 5」;
单光标显示 `4.9417` + `4.94199(中心轴)`;
双光标(标题带「(光标区间内)」,计数 341959/341957)右侧光标显示
`4.9425 (+0.000…)` + `4.94214(+0.00… 中心轴)`,左侧光标无增量;
把 N 改为 4000 后曲线与计数同步更新(2333700/2333700);
用选区放大把光标推到视野外后,中心轴数据点在与信号相同的贴边位置绘制为**橙色三角**
(像素扫描 x=479 处得到 6px 高的 `#CB4B16` 三角,而非常规圆点)。
- 回归:[1].CSV 上「边沿计数」面板正常(上升沿 2 / 下降沿 2),中键平移正常。
- make clean && make all 无警告无错误。
|
||
|
|
5182bd8aa3 |
实验性功能完善:面板交互修正 + 新增「边沿计数(中心轴)」
一、实验性功能浮动面板的交互修正(「边沿计数」与「边沿计数(中心轴)」共用) - 面板只有**标题栏**可以拖拽:鼠标在标题栏显示抓握光标(IDC_SIZEALL), 在面板主体(含统计行与底部编辑行)保持原始箭头,按下主体也不再拖动面板(仅消费点击, 不会穿透到底层的平移/选区/信号拖拽)。 - ✕ 关闭按钮:鼠标悬停时高亮(danger 色底 + danger 色叉),但**不改变鼠标光标样式**。 - 为此新增 `EdgePanelLayout`(panel / title / close / edit / rows)与 `EdgePanelLayoutFor(laneIndex, centerAxis)`,`QueryCursor`、`OnMouseMove`、`OnLButtonDown` 与绘制统一按该布局判定。 二、新增「实验性功能 → 边沿计数(中心轴)」(带 √ 的切换项,按网格独立) - 开启后为网格内**每条信号**叠加绘制一条"中心平均值曲线": 每个数据点 = 自身与左右各 N 个数据点(共 2N+1 点,边界处按实际窗口)的平均值。 用滑动窗口求和构建,整体 O(n),不额外占用前缀和内存;结果按 (信号, N) 缓存 (deque 保证引用有效),数据层变化或 N 改变时清空。 - 该曲线只是叠加显示,**不会**加入左侧信号列表; 为与波形区分,颜色按背景明暗把信号色向黑/白方向拉远(浅色主题加深、深色主题提亮), 线宽略粗于原信号。 - 纵向光标仍会标注这条曲线的取值:在原取值胶囊之外额外绘制一个「中 <值>」胶囊 (与曲线同色、半透明),并复用统一的避让逻辑(PlaceCursorLabel,已抽成函数)。 - 统计规则:**信号从下往上越过中心平均值曲线 → 上升沿;从上往下穿过 → 下降沿** (比较 `signal[i] - center[i]` 的符号变化,NaN 视为断点)。 - 浮动面板标题为「中心轴边沿计数」,样式与「边沿计数」面板一致(GX 徽标 + 标题 + ✕ + 每信号一行名称与「上升沿 N / 下降沿 M」,数字完整不省略、面板宽度按文本实测自适应); 面板**底部固定一行「平滑点数 N」内联编辑设置**(新增 EditFilter::NUMBER 编辑目标 PlotEditTarget::EDGE_CENTER_SMOOTH,夹在 0–1000000),始终位于面板最下方。 - 与双光标模式联动:双光标时标题追加「(光标区间内)」,只统计左右光标之间的区间; 区间端点恒取两光标时间的 min/max,因此把任一光标拖到另一侧(左右交换)后依然正确。 - ✕ 关闭面板的同时关闭该网格的「边沿计数(中心轴)」功能(面板与曲线一并消失)。 验证(TestData/VERY_BIG_DATA.CSV,编码器A相,125%、1725x996): - 面板光标形态探测:主体 = ARROW、标题栏 = SIZEALL、✕ = ARROW; 从主体拖动面板不发生位移(截图核对)。 - ✕ 悬停高亮可见。 - 「边沿计数(中心轴)」:面板标题「中心轴边沿计数」、底部「平滑点数 N = 5」; 中心平均值曲线(深蓝)清晰可见;把 N 改为 3000 后计数同步更新。 用独立脚本 .preflight/centerprobe.ps1 按同一规则复算,与界面**逐位一致**: N=5 → 上升沿 2712920 / 下降沿 2712913;N=1000 → 2373176 / 2373176。 - 双光标联动:区间 [48.0211, 64.4051](面板标题带「(光标区间内)」)→ 349220 / 349220; 把光标 1 的时间改到 100(越过光标 2 的 64.4051)后,区间变为 [64.4051, 100], 计数 778085 / 778084,与按该区间复算的结果一致(±2 为光标时间显示值取整导致的边界差)。 - 纵向光标读数:右光标显示 `4.9417 (-0.071945)` + `中 4.94199`, 左光标显示 `中 2.9165`(中心曲线取值单独标注)。 - 左侧信号列表仍只有原信号,未新增曲线条目。 - 回归:[1].CSV 上「边沿计数(光标区间内)」正常(Δt:151ms 200us); 「边沿计数(中心轴)」在 [1].CSV 上曲线清晰可见(上升沿 199 / 下降沿 199)。 - make clean && make all 无警告无错误。 |
||
|
|
cd6e0ed058 |
界面增强:网格图例 GX 编号、工具栏状态修复、增量括号、实验性边沿计数
- 每个网格图例左侧新增当前网格的 “GX” 编号徽标,与左侧信号列表行末的 G1/G2 徽标
使用完全一致的样式(同宽高、同圆角、同配色、同字体),并纳入图例底板范围。
- 修复工具栏 toggle 状态被重置的缺陷:Toolbar::Layout() 会重建全部按钮,
原先会把 checked 全部清零。因此当「光标」按钮因为模式变化(光标x2 → 光标)
触发文本变更与 Relayout 时,同帧刚设置好的「Y 轴自适应」「选区放大」勾选态被清掉,
表现为切换光标模式时前面两个开关被一并取消。现在 Layout() 会按 action 继承既有
checked 状态,重建后状态保持不变。
- 双光标右侧光标的增量显示去掉逗号,改为包裹在圆括号中:`4.9425 (+0.0008)`。
- 网格右键菜单在分隔横线上方新增「实验性功能」(带二级子菜单指示箭头):
鼠标悬停即向右展开二级菜单,其中包含「边沿计数」切换项(带 √)。
为此为 PopupMenu 增加了完整的二级子菜单支持:子菜单排版(贴主菜单右缘、
右侧空间不足时翻到左侧、限制在宿主窗口内)、悬停展开/移开收起、
点击父项不下发命令、子菜单内按下/抬起配对提交、键盘(← 收起 / → 展开 /
↑↓ 在子菜单内移动 / Enter 提交 / Esc 先收起子菜单再关菜单)、
独立的投影与绘制、ContainsPoint 覆盖子菜单区域、Relayout 后子菜单位置重建。
- 「边沿计数」开启后(按网格独立,状态随 RebuildLanes 按信号继承):
* 在该网格绘制一条横向光标,风格与纵向时间光标一致(同色同线宽),
左右两端各带一个尖端朝内的三角把手。
* 左端显示当前光标的 Y 轴值,读数胶囊纵向居中于横线、靠网格左侧。
* 横向光标不绘制任何数据点取值标注(与纵向光标的行为不同)。
* 横向光标可上下拖动改变幅值,拖动时显示上下箭头光标。
* 生成带 ✕ 关闭按钮的浮动面板:标题栏显示 “GX” 徽标 + “边沿计数”,
下方按信号列出名称(信号色)与「上升沿 N / 下降沿 M」。
面板可在整个绘图区内拖动(限制在绘图区内),✕ 仅收起面板、保留横向光标,
重新勾选菜单项可再次展开。
* 边沿判定阈值为信号幅值的 (min+max)/2 中点穿越,NaN 视为断点;
统计结果按 (文件, 信号) 缓存,数据层变化时随折线缓存一并失效。
验证(TestData/VERY_BIG_DATA.CSV,编码器A/B相密集方波,125%、1725x996):
- 图例编号:G1 / G2 徽标与左侧列表徽标样式一致。
- 工具栏:开启「Y 轴自适应」+「选区放大」后,光标由 光标x2 切回 光标,
两个开关仍保持勾选(截图核对)。
- 增量:右光标显示 `4.9425 (+0.0008)`。
- 二级菜单:悬停「实验性功能」展开「边沿计数」,箭头与子菜单位置正确。
- 边沿计数:横向光标 + 左端读数 `2.54415` + 面板
`G1 边沿计数 / 编码器A相 / 上升沿 1333422 下降沿 1333421`;
上下拖动后横线上移到 4.13602(与 Y 轴刻度一致);面板拖到绘图区左下并保持在内;
点击 ✕ 后面板收起而横向光标保留;
光标形态探测:横线悬停 SIZENS、面板悬停 SIZEALL。
- 合并网格后面板同时列出网格内两条信号(编码器A相 / 编码器B相)。
- 回归:[1].CSV 双光标正常(光标x2),Δt:151ms 200us;
make clean && make all 无警告无错误。
- 修复过程中发现:静态场景缓存命中路径(贴图快路径)与整幅重绘路径是两处独立的
叠加层绘制入口,新增的随鼠标变化内容必须同时加在两边。
|