Commit Graph
51 Commits
Author SHA1 Message Date
DeepSeek V4.1 Flash 79eebc281e 自由标记:面板表头/吸顶/行选中与悬停联动 + 原位显示 + 探测容差与阶跃吸附
- 面板:每个展开分组下方新增一行列表头(标记值/枚举文本/增量/时间点/时间间隔),
  与数据行共用列边界;列宽同时容纳列标题;表头行计入内容总高与面板高度;
  全部无枚举文本时仍整列缺省
- 吸顶:分组栏与表头各自按 max(自然, min(吸顶, 组下边界-自身高)) 钉住,
  组末尾按"表头先走 -> 条目 -> 分组栏最后离场";绘制顺序改为条目->表头->吸顶分组栏,
  被盖住的条目不绘制也不可命中
- 行交互:Lane::freeMarkerSelected(单击选中/再点取消)+ 独立的整行悬停状态
  (整行悬停不会把该行 ✕ 变成 danger 色);悬停时网格内对应固定点整体高亮
  (数据点圆环 + 胶囊强制不透明 + accent 描边,仅视界内)
- 固定点恢复原位显示(删除纵向避让,允许遮挡,回到初版方案)
- 探测判据重写:鼠标到画出来的折线的垂直距离 <= kSignalHitTolerance(与曲线高亮同源),
  命中后吸附到最近采样时刻 => 修复阶跃竖直跳变线无法探测/固定的问题;
  删除已无用的 kFreeMarkerHoverTolerance
- 修复滚动时条目文字串到面板标题栏(GDI 文本不认 GDI+ 裁剪,改用 UiTextClipped)
- 顺带清理过期注释(四列->五列、折起指示器已无横线、探测判据说明),
  RemoveFreeMarker 改为复用 RemoveFreeMarkerAt(选中下标校正只有一处)
- 验证:新增离屏 .preflight/mark4check 38/38(三组 A/B 均如实失败);
  回归 markcheck 61/61、fixcheck 37/37、round3check 31/31、round4check 34/34、
  round5check 5/5、editcheck 20/20、convcheck 18/18;make release/debug 零警告
2026-10-22 21:04:03 +08:00
DeepSeek V4.1 Flash 8f735d51be 修复拆分网格误继承实验性功能 / 阶跃线宽 / 面板拖拽残影 / 自由标记卡顿与交互
- 拆分网格:实验性功能只由原网格的首个信号继承(LaneCarry::owner);
  SplitLane 只让第一份继承;新增 PruneLaneSignalItems 按 lane.signals 过滤四类
  逐信号条目(修复"新网格出现别人的固定点、面板里却查不到")
- 阶跃信号:包络带横向膨胀半径 ceil -> floor(1px 线宽不再把竖直沿撑成 3px)
- 浮动面板:面板位置移出 PanelGlobalKey(拖动单个面板不再重画全部面板),
  新增 vacatedRects 让"上一帧被移动/缩小/关闭的面板腾出的区域"也强制重画
- 自由标记:新增 FreeMarkerContents 内容缓存(键不含视界),大量固定点时拖光标/平移不再逐帧采样;
  透明度滑块与光标模式解耦(始终显示);悬停胶囊恢复不透明(瞄着被遮住的数据点时除外);
  胶囊与面板五列按实测文本撑开,不再省略
- 面板交互:折起指示器与信号列表组名同款三角;GX 徽标/标题栏悬停不改光标、移除徽标气泡;
  标题栏双击滚动到该网格(单击不触发,双击不再折起)
- 验证:新增离屏 .preflight/fixcheck 37/37(三处 A/B 均能如实失败);
  回归 round3check 31/31、round4check 34/34、round5check 5/5、editcheck 20/20、convcheck 18/18;
  make release/debug 零警告
2026-10-21 17:21:09 +08:00
DeepSeek-V4.1-Flash 3400ac7c29 自由标记体验重做 + 全部实验性面板可折起
- 网格内固定点:取值胶囊按已放置矩形做纵向避让(不再互相遮挡);行尾 ✕ 改为与
  胶囊同高的正方形;移除固定点的纵向半透明竖线
- 新增「标记透明度」(0–100%),作用于本网格全部固定点的圆点/胶囊/描边/文字/✕;
  半透明时文字改走 GDI+(GDI DrawTextW 忽略 alpha),100% 时像素零变化;
  ≤2% 时不绘制且不登记 ✕ 命中矩形(看不见的按钮不可点)
- 面板按信号分组:信号色托底分组栏(展开三角 + 信号名 + N 标记点 + ✕,文字/三角/✕
  一律用自适应对比色),点击分组栏收起/展开;条目五列(值/枚举/增量/时刻/间隔),
  首项增列与 Δt 显示 N/A,整个面板没有枚举文本时枚举列整列缺省;行尾仍有 ✕
- 面板内容超高时右侧出现滚动条(分组栏与条目一起滚动),滚轮在内容区翻页;
  底部按钮与「标记透明度」滑块固定在底部、不参与滚动
- 底部按钮改为「光标选取模式」切换类按钮(外观与工具栏切换按钮一致,激活高亮),
  激活时其上方出现「标记透明度」滑动条(与信号样式区的透明度滑块同款)
- 所有实验性功能浮动面板支持展开/折起:标题栏 GX 徽标左侧新增「三角 + 横线」指示器,
  单击切换、双击标题栏切换;折起的面板只有标题栏
- 数据层:LanePanelState.collapsed、Lane.freeMarkerOpacity/freeMarkerScroll/
  freeMarkerCollapsedGroups,LaneCarry 与 SplitLane 一并继承
- 验证:新增离屏 .preflight/perf/mark2check 69/69(含两组 A/B);回归 datacheck 69/69、
  verify 49/49、panelanim 158/158、errcheck 19/19;make release/debug 零警告
2026-10-21 02:59:05 +08:00
DeepSeek V4.1 Flash 067128f46c 自由标记改进:默认不启用光标模式 + 面板四列对齐 + 光标模式切换按钮
用户需求(逐条落地):
1) 默认不启用网格私有光标,只有鼠标处于信号上方时才显示数据点数值;命中
   「自动点线」稀疏判据的信号,只有靠近数据点时才有数值显示;
2) 有数值预览时左键单击固定该数据点(固定点样式不变:半透明纵向细线);鼠标
   下方没有信号曲线时,按下/拖动就是普通的平移视界(选区放大模式下照旧框选);
3) 浮动面板最下方新增切换按钮「启用光标模式」↔「关闭光标模式」;启用后与最初
   实现一致(跟随鼠标的纵向自由光标),并修掉「光标模式下拖不动浮动面板」;
   鼠标处于浮动面板上时网格不画自由光标,面板上的操作不穿透到网格;
4) 面板只列出确实有固定点的信号(否则该信号名条目不出现);
5) 每个信息元素纵向对齐:取值 / 增量 / 时刻 / 间隔各自一列。

数据层
- Lane 新增 bool freeMarkerCursor(默认 false,功能总开关仍是 freeMarker);
  LaneCarry 同步携带。
- PlotView 新增 SetLaneFreeMarkerCursor / LaneFreeMarkerCursor /
  ToggleLaneFreeMarkerCursor,切换时立即作废属于该网格的探针。

探针与绘制
- FreeMarkerProbe 新增 cursorMode。
- 非光标模式:稀疏判据成立但吸附失败 ⇒ 跳过该信号;其余要求鼠标与曲线纵向距离
  在 kFreeMarkerHoverTolerance(8 逻辑像素)以内;都不满足 ⇒ 探针无效 ⇒ 按下即
  普通平移/框选,不会误固定数据点。
- 光标模式:与之前实现一致(网格内始终有效)。
- PaintFreeMarker:纵向线 + 上下把手只在光标模式绘制;取值胶囊/数据点/吸附圆环
  两种模式都画。

浮动面板
- 底部新增「启用光标模式 / 关闭光标模式」切换按钮(layout.button),手型光标 +
  悬停高亮,PanelSlotKey 混入 freeMarkerCursor。
- 只给有固定点的信号发名称行(高度分支与矩形分支同步跳过空信号)。
- 四列固定横坐标(EdgePanelLayout::markerColumns:取值 / 增量 / 时刻 / 间隔),
  面板宽度 340 → 415(= 4 列 + 间隙 + 行 ✕ + 两侧内边距);绘制一律左对齐 +
  省略号截断,双击分行改按列边界判断(不再用 xxxRight - 40*scale 反推)。

修复的缺陷(用户报告)
- 「光标模式下拖不动浮动面板」的真因不是命中判定,而是 OnMouseMove 的
  EDGE_PANEL_MOVE 分支里 switch (m_edgePanelDragKind) 漏了 MARK:拖动坐标被写进
  lane.edgePanel,而布局从 lane.freeMarkerPanel 读 ⇒ 面板纹丝不动。已补 case。
- 鼠标压在浮动面板上时:OnMouseMove 先 TopPanelAtPoint,命中即清空探针并跳过固定
  点 ✕ 的悬停判定;OnLButtonDown 的自由标记分支增加 freeOverPanel 守卫,
  确保面板操作不穿透到网格(也不再绘制穿过面板的自由光标)。

验证(全程离屏,不占用鼠标/键盘/屏幕)
- .preflight/markcheck 重写并扩到 61/61:默认无光标、贴近曲线才有效、远离曲线单击
  不固定且拖动仍是平移、四列几何对齐、按钮切换光标模式、光标模式下拖动面板确实
  移动、面板上不画光标、选区放大共存、Δt 编辑重排、两处 ✕、空信号不出行、深度
  放大吸附、视界外固定点不绘制。
- 回归:editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、
  convcheck 18/18、check 136/137(唯一失败项是此前就存在的不稳定断言)、真实
  MF4-Record.MF4 仍 283 条信号。
- make release / make debug 零警告;Build/Release/Target.exe 834,560 字节。
- 同步更新 Documents/agent_mem.md(§11.2 第 189–193 条 + 文末本轮章节)。
2026-10-20 20:39:42 +08:00
DeepSeek V4.1 Flash dd8f63aa0e 新增实验性功能「自由标记」:网格内跟随鼠标的纵向光标 + 单击固定数据点 + 浮动面板
需求(用户):网格右键菜单「实验性功能」里新增带 √ 的「自由标记」。开启后网格内跟随鼠标
出现一条仅限本网格的纵向光标(上下各一个把手、不显示时间值),穿过信号时只标注离鼠标
最近的数据点;左键单击把该点固定(带 ✕ 可移除,鼠标移开仍显示、超出视界直接不显示),
后续固定点显示为相对前一个的增量;面板按信号分组列出「值(±增量) = 枚举」+「t:… Δt:…」
(两列右对齐、可双击按秒编辑并自动换位);命中自动点线判据的信号要有吸附提示;
两级 ✕ 都要悬停提示;与选区放大共存时原地单击 = 固定、按下拖动 = 框选。

一、数据层
- Lane 新增 freeMarker 开关、freeMarkerItems(FreeMarkerItem{ref, time})、freeMarkerPanel。
  time 是「显示时刻」(画在 TimeToX(time) 处),与光标同一口径;取值必须走
  SampleDisplayedSignalAt(内部处理时间偏移与水平镜像)。
- 新增 AddFreeMarker / RemoveFreeMarker / RemoveFreeMarkerAt / RemoveFreeMarkersOf /
  FindFreeMarker / HasFreeMarkers。同一信号同一时刻不重复加入(相对容差 1e-12);
  freeMarkerItems 保持插入顺序,显示顺序与「前一个固定点」一律由 SortedFreeMarkerIndices
  按时间排序得出(删除某项不打乱其它项下标)。
- LaneCarry 同步携带,勾选/取消勾选信号导致网格重建时不会丢掉已固定的点。

二、网格内交互
- ProbeFreeMarker:仅当鼠标所在网格开启该功能时有效;在网格内全部可见信号里挑屏幕纵向
  距离鼠标最近的数据点。MinorCellSparse 成立时经 SnapTimeToSample 吸附到真实采样时刻,
  绘制侧画一圈圆环作为吸附提示。吸附只看判据,不看当前绘制方式。
- PaintFreeMarker(接在 paintOverlays 里、浮动面板之前):纵向线只在本网格内,上把手尖端
  朝下(与常规光标顶部把手一致)、下把手尖端朝上,不画时间读数;已固定点画低透明度细线 +
  圆点 + 「值(±增量) = 枚举」胶囊 + 行尾 ✕(悬停高亮 + 提示气泡);
  超出视界直接不显示(不复用常规数据点的贴边定格 + 方向三角)。
- 网格内固定点 ✕ 的命中矩形由绘制时登记(m_freeMarkerHits),因为它的位置取决于取值文本宽度。
- OnMouseLeave 清跟随光标与三处悬停态(已固定的点保留)。

三、单击 vs 框选
- OnLButtonDown 在开启该功能的网格上只登记 m_freeMarkerPendingFix,并把拖拽模式设为
  「选区放大 ? RUBBER_SELECT : PAN」;OnLButtonUp 里只有「整段操作没有拖动且选区轴向未定」
  才 AddFreeMarker,否则照旧框选/平移。

四、浮动面板(EdgePanelKind::MARK / PLOT_CMD_LANE_FREE_MARKER = 12)
- 每个可见信号一行名称(右侧带清除该信号全部固定点的 ✕),名称行下方按时间升序列出该信号的
  固定点;没有任何固定点时第一行给灰色提示。
- 行 = 左侧「值(±增量) = 枚举文本」(无尖头括号、首项无增量)+ 右侧两列各自右对齐的
  「t:<时刻>s」与「Δt:+<复合单位>」(自动单位匹配)+ 行尾 ✕。
  两列右边界存进 EdgePanelLayout(markerTimeRight / markerDeltaRight),保证所有条目纵向对齐。
- 双击行:左半编辑自身时刻(FREE_MARKER_TIME),右半编辑相对前一固定点的间隔
  (FREE_MARKER_DELTA,最左边的点没有前一项所以不进入编辑)。提交后只改 time,
  顺序由排序函数自动重排(越过后续点即交换条目位置)。
- PanelSlotKey 把每个固定点的信号 + 时刻、全部 marker 行矩形与两列边界一起 mix。

五、一并修掉的两个自伤
1. EdgePanelWidth 的 MARK 分支原本逐行取采样值算文本宽度 —— 它被 EdgePanelLayoutFor 调用,
   而后者在 TopPanelAtPoint / SyncPanelOrder 里每次鼠标移动调用数次,等于「鼠标一动就做几十次
   采样查询」。改为固定宽度 kFreeMarkerPanelWidth(340 逻辑像素)+ 绘制侧 UiEllipsize。
2. PaintFreeMarker 与跟随光标的胶囊绘制原本是两份重复代码,抽 PaintFreeMarkerCapsule 共用;
   FreeMarkerLabelRects 增加 text 出参,避免绘制侧重复求解一次取值。

验证(全离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/markcheck 31/31:探测有效与就近选择、原地单击恰好新增 1 个固定点、
  选区放大共存(单击固定不缩放 / 拖动缩放不固定)、面板行数与时间升序、首项无增量、
  后续项增量与 Δt 正确、两列右对齐、行尾 ✕ 只删 1 项、信号名 ✕ 清空该信号、
  编辑 Δt 后移动并重排、深放大时吸附且时刻精确等于采样时刻、视界外固定点不绘制。
- 回归:editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、
  convcheck 18/18;check 136/137(唯一失败项是本轮之前就存在的不稳定项,上一轮已用
  git stash 在基线复现同一数值);真实 MF4 仍 283/8/18/24 个信号。
- make release / make debug 零警告。
- TargetCheck.exe 833,536 字节(814.0 KiB)—— 比上一轮 +24 KB,首次超出旧的 800 KiB
  红线约 14 KB。已做的瘦身见上;剩余为新功能的真实代码量,若要回到 800 KiB 以内需要
  改变取舍(动态 CRT / UPX / 砍其它功能),本轮按「实现功能优先」未动。

文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 181-188 条硬约束
与文末本轮小节。
2026-10-20 19:47:55 +08:00
DeepSeek-V4.1-Flash 181f95cc3a MF4 补 ##SD/##SI/VLSD + 信号枚举文本 enumText + .mat 改随机读(流式进度)
1) 修复上轮记录的两个已知问题
   - MatLoader: LoadPlainObject 不再为拿 dims 调 ReadDataset(整块解码第二次),
     改用 ReadDatasetInfo;普通 v7.3 大矩阵的时间/内存不再翻倍。
   - MdfFile::TextBlock 注释与实现对齐:真的支持 ##MD(UTF-8 XML,无长度前缀)。

2) 信号枚举信息(以 TestData/NEW-MF4.mf4 为目标)
   - 新增 SignalEnumText(DataTable.hpp):values[] + texts[] + Empty()/Find(),
     命名用 enumText —— 它描述"取值代表哪个状态",不是单位。
   - DataTable::columnEnumText 与 columns 同下标;SignalData::enumText 由
     BuildSignalGroup 移动赋值;其它格式(CSV/.mat)保持为空。
   - MF4 cc_type = 7:cc_val[i](F64,数据段 +24)↔ cc_ref[4 + i](##TX/##MD 文本),
     最后一个引用是默认文本,不使用。
   - 界面只在**光标取值标注**追加 " = <枚举文本>"(等号两侧各一个空格、无尖括号),
     查表用采样原值(ApplyDrawTransform 之前);常规状态显示一个像素都不变。
   - 实测:NEW-MF4.mf4 8 信号 / 4 条带枚举(32+5+32+5),MF4-Record.MF4 另有 2 种(12/32)。

3) MF4 支持范围
   - ##SI 采样索引:只有索引 ≥2 条且全部 < 周期数才生效(逐记录有效性位图)。
     实测部分工具给每个通道挂只含常量 513/4 的 ##SI,必须忽略,否则整列变 NaN。
   - ##SD + VLSD(cn_type == 1):##SD 数据段是「UINT32 长度 + 字节」样本序列,
     记录里放 64 位偏移;AppendSignalData 递归展开 SD/DZ/DL/HL(单通道上限 512MB),
     DecodeVlsdValue 先按直接偏移、失败再按 >>1 回退,取样本第一个元素。
   - CN 的 cn_data / md_unit / md_comment 下标按链接总数反推:8 链接(MDF 4.0 风格)
     = 换算[4]/数据[5]/单位[6]/注释[7],9 链接(4.1)= [5]/[6]/[7]/[8]。
     实测 Simulink 导出的“4.10”仍写 8 链接,不能写死 Link(cn,4/5)。
   - 主通道:cn_sync_type 1/2/3(时间/角度/距离)→ 名字 t/time/timestamp/zeit/时间
     (含 t[...])→ 第一个数值通道兜底;cg_flags 第 2 位(远程主通道)跳过 sync 判定。
   - 记录长度优先 cg_data_bytes(CG 数据段 +24);旧实现把 U8(cgData) 当
     cg_record_id_len,其实是 cg_flags 低字节(总线事件位)。
   - 进度:先走 DG 链得到真实组数(不再写死 64),hasBytes = true,bytesRead 取
     MdfFile::ReadAt 累计(夹到文件大小);4MB 分块读取检查取消。
   - ##DZ:数据段前有 28 字节头(original_type/zip_type/param/original_size/zip_size),
     旧实现整段当 zlib 流是错的;只支持纯 deflate(zip_type = 0)。
   - 一个信号都没有时,错误信息附带「布局无法解析 / 缺少时间通道 / 空数据组」计数。

4) .mat:内存映射 → 随机读 + 元数据块缓存(消除 SEH 风险)
   - MatHdf5::Open 改为 CreateFileW(FILE_FLAG_SEQUENTIAL_SCAN) + ReadFile;
     删除 MappedBytes,改为 ReadAt / Cached(16 × 64KB LRU + 上次命中槽)/ Tag /
     ReadData / ReadCString。I/O 失败只会变成 false,不再触发页访问异常。
   - 数据读取:连续布局按 4MB 切片;分块布局按需 + 64KB 对齐。
     固定 4MB 预读在交错存放的分块数据集上会把读盘量放大到 2.2 倍(2874MB/1293MB),
     改后 1301MB。
   - 关键提速:dims=[N]、chunk=[32768,1] 归一化后 rank=2、每行 1 字节,逐行算坐标
     是 12.9 亿次迭代;新增「rank == 2 且 chunkDims[1] == dims[1]」快路径(整块一次
     sink)。BIG_MAT.mat(1.29GB / 5 × 16,142,471):21.5s → 7.9s,私有内存 +741MB。
   - 进度:Open 立刻上报「0 / 总大小」,之后每读一块回调 ReadProgress(bytesRead,total)
     → MatLoader 映射到 0.02–0.98 并单调夹紧(字节进度与逐流解码取较大值),收尾 1.0。
     遮罩层百分比不再一上来就 100%;取消会中止(StreamDataset 返回 false),不留 NaN 空洞。
   - 连续布局数据不足(文件被截断)现在明确失败,不再静默截短。

验证(全部离屏:无可见窗口、不注入鼠标键盘屏幕)
- 新增 .preflight/perf/loadercheck(数据层)32/32:
  NEW-MF4 8 信号 / 4 条枚举 / 枚举文本逐条核对 / 9396 与 94 行;
  MF4-Record 124 信号 / 3 轴 / 4 条枚举;data-sample.mat 4 组 8 信号 / 行数;
  BIG_MAT.mat 5 信号 × 16,142,471 + 进度(total 已知、ratio 单调、bytes 递增、
  起点 ≤0.05、终值 1.0、更新 ≥10 次)。
- 新增 .preflight/perf/enumui(真实 Application + SW_HIDE)12/12:
  只显示 gVR_WinOpenReq[0],光标落在枚举采样点上 —— 标签宽度 265px;
  临时清空 enumText 后 25px;差值 240px 与 " = WinOpenReqAtDrvr_Idle" 的
  UiMeasureTextWidth 完全一致;恢复后宽度回到 265px。
- 回归:datacheck 69/69、matcheck 21/21、matui 15/15(loading bytes 达到总大小)、
  verify 49/49。
- make release / make debug 零警告。Build/Release/TargetCheck.exe = 820,224 字节
  (上轮 785,920,+34KB)。(Target.exe 正在被用户运行的程序占用,链接步骤未覆盖它。)
2026-10-20 01:56:47 +08:00
DeepSeek V4.1 Flash 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 条与变更日志一行。
2026-10-16 11:06:31 +08:00
DeepSeek V4.1 Flash 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:聚合化、解耦、探针、验证结果、变更清单)。
2026-10-16 04:38:12 +08:00
DeepSeek V4.1 Flash 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 历史附录加“时效声明”。
2026-10-16 03:25:48 +08:00
DeepSeek V4.1 Flash 68d8b7ede6 自适应范围/时机澄清 + 适应视图与 Y 轴自适应互不干扰 + 吸附跟随时间偏移
一、澄清「自适应范围与时机」(并修正记忆里不完善的表述)
 * 准确说法:「额外要求视界内至少有一个可见数据点」这句不完善,完整规则是:
   - 首个网格显示内容时对它做完整自适应 —— **Y 轴与时间轴都**匹配自己的数据范围;
   - 只有一个数据点的信号必须显示在网格**中间**,保证其可见
     (FitSinglePointSignal:数据点居中、时间跨度 1s、Y 轴跨度 1);
   - **后续新增网格只做 Y 轴自适应,不再调整时间轴**。
 * Documents/agent_mem.md 相应章节(§9.30 正文、§11.2 第 58/60 条)已按此改写。

二、修复「适应视图」与「Y 轴自适应」互相干扰
 * 根因:两条自适应路径的留白不同 —— 整段自适应(ComputeLaneRange → PadRange)用 6%,
   「选区 Y 轴自适应」(AutoRangeLaneToView)用 5%。视界未变时两者本应完全相等,
   于是「点过适应视图 → 打开 Y 轴自适应」会算出不同区间、网格被无意义重绘
   (实测切一次 143513 像素),反向操作同样(137326 像素)。
 * 修法:新增 DataModel.hpp 的 PadValueRange() 作为**唯一**留白实现(含退化区间规则
   max(1, |值|×0.1)),PadRange 与 AutoRangeLaneToView 共用它。
 * 结果:「适应视图 → 开自适应 → 关自适应 → 再适应视图」四个步骤上生效区间逐位相同,
   绘图区逐像素 0 差异;而真正改变视界(缩放)时仍然正常重绘。

三、修复「适应视图」在单点信号上让数据点“飞了”
 * FitAll() 开头先尝试 FitSinglePointSignal():仅一个网格、且唯一可见信号只有一个有效
   数据点时,走与首次加载完全相同的单点初始视图(数据点居中、时间跨度 1s、Y 跨度 1),
   并保持幂等(重复点击像素不变)。

四、修复光标吸附未考虑「调整 → 时间轴偏移」
 * 根因:采样点 k 画在 time[k] + offsetTime 处,而光标存的也是显示时间,
   但吸附一直在源时间空间查找 —— 设过时间轴偏移后吸附会落到历史位置。
 * 修法:SignalSnapEnabled / HasVisibleSample / NearestSnapSample / LocalSampleSpacing
   全部增加 timeShift 形参:可见性与点数统计比较 time[k] + timeShift;
   NearestSnapSample 内部用 time - timeShift 回源空间查找、返回时再加回去;
   UpdateAutoScatterStyles(自动点线)同步传入 offsetTime,保持与吸附判定同源。

五、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
 39/39 通过(连跑 3 次稳定):
 [A] 四个步骤的生效区间逐位相同 + 绘图区逐像素 0 差异(对照组:真缩放重绘 79581 像素);
 [B] 单点信号的适应视图与首次加载一致(1s/1、居中、幂等);
 [C] 首个网格自适应时间轴、后续新增/隐藏信号都不再改动时间轴;
 [D] 偏移 +0.3 时吸到“画面上的点”,偏移 0 行为不变,偏移把点移出视界时不吸附;
 [E] 回归:单点默认样式(点线 + 最大线宽)与“2…16 点不再改点线”均保持。
 A/B:回退三项修复 ⇒ 7 项如实失败(含 143513 / 137326 像素的重绘症状)。
 make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。

六、规则同步
 Documents/agent_mem.md 新增 §9.31、§11.2 第 58–63 条(含修正后的自适应范围/时机规则)
 与变更日志。
2026-10-15 15:46:14 +08:00
DeepSeek V4.1 Flash 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 条硬约束与变更日志。
2026-10-15 15:10:36 +08:00
DeepSeek V4.1 Flash 9f6de3bba4 全局 Y 轴自适应跟随网格结构 + 组名悬浮 + 稀疏信号默认点线 + 单点光标吸附 + 放大自动点线
一、全局「Y 轴自适应」跟随网格结构
 * 新勾选信号产生的新网格现在会**继承**该开关:RebuildLanes 重建前先算
   previousAllAutoFit(非空且每个网格都开着 = 全局开启的正式定义),LaneCarry 加
   matched 标记,新出现的信号用它作为 fitYToSelection 的默认值。
 * 新增 PlotView::ReapplyGlobalAutoFitIfEnabled(),在 Layout()(网格数量变化 →
   Relayout)、RefreshLanes()(信号数量变化)、SplitLane() 之后立即重算一次。
   ⇒ 新勾选(按当前视界自适应)、多信号网格内取消显示某信号(剩余信号重新收敛)、
     拆分网格(每份各自自适应)三项都覆盖。

二、信号列表「组名悬浮」
 * 分组行被滚出列表上边界、而该组仍有信号留在列表里时,把分组行钉在列表顶部
   (不透明底 + 底部边界线);整组翻过去后本组组名消失,改由下一组悬浮。
 * 新增 SignalPanel::StickyGroupRowIndex() / PaintStickyGroupRow();RowRect 增加
   m_rowYOverrideActive/m_rowYOverrideY 覆盖行顶部坐标(RowRect 是行内名称框 / ✕ /
   徽标的唯一几何来源)。常规行循环跳过被钉住的那一行,避免同一行被画两次。

三、「少数数据点」信号的默认样式
 * 有效采样点 1 个 → 「点线」+ 最大线宽(10px);2 … 16 个 → 「点线」+ 2px;
   其余保持「线段」+ 1px。只在新建信号时生效(BuildSignalGroup 与
   InsertDerivedSignal),不覆盖用户设置。
 * 线宽范围常量统一到 DataModel.hpp(kSignalLineWidthMin/Max),SignalPanel 的线宽
   滑块改为引用它们(原本两处各自硬编码,容易漂移)。

四、单点信号的光标吸附(吸附而非卡住)
 * 旧逻辑要求时间轴至少 2 个采样点才能算「采样间距」,单点信号因此永远不吸附。
 * 新增 SignalData::singlePointTime(加载时与 ComputeStats 同趟扫描缓存该点时刻),
   SnapTimeToSample 新增半径分支(kCursorSnapPointRadiusPixels = 8 逻辑像素):
   半径内吸附到该点,半径外自由移动(移开即自由,不是把光标吸住)。

五、放大到「采样点稀疏」时自动启用点线
 * SignalStyle 新增运行态三字段:autoScatterActive / autoScatterSavedMode /
   autoScatterUserOverride。
 * AxisSparseInView()(采样点间距 > 半个绘图区宽度,与光标吸附上门限同源;单点时间轴
   不算稀疏)+ UpdateAutoScatterStyles():进入阈值保存原绘制方式并切「点线」,
   退出阈值(如适应视图)恢复;平移不改变缩放等级 ⇒ 结果稳定,不会样式抖动。
 * 自动启用期间用户手动改了绘制方式则退出时**不恢复**(尊重用户选择);该判定必须在
   恢复判断之前完成 —— 用户改完样式可能紧接着就退出阈值,否则会被覆盖。
 * 钩子:SetTimeView(视界唯一写入口)与 Layout / RefreshLanes / SplitLane。

六、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
 65/65 通过(连跑 3 次稳定):[A1]/[A2]/[A3] 全局自适应、[B] 组名悬浮(含钉住行与自然
 位置逐像素一致、整组翻过后组名消失)、[C] 稀疏默认样式边界(1/2/5/16/17/1000 点)、
 [D] 单点吸附(半径内吸附、半径外自由、无残留粘滞)、[E] 自动点线(进入/退出/平移稳定/
 用户手动改过后不覆盖/再次进入再启用)。
 A/B:临时禁用六个机制后重跑 ⇒ 21 项如实失败(覆盖全部六项),证明断言有判别力。
 make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。

七、规则同步
 Documents/agent_mem.md 新增 §9.29、§11.2 第 55–58 条硬约束与变更日志。
2026-10-15 14:10:13 +08:00
DeepSeek V4.1 Flash 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 零警告。
2026-10-15 03:48:58 +08:00
DeepSeek V4.1 Flash 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 条硬约束、
变更日志补一行。
2026-10-14 15:24:19 +08:00
DeepSeek V4.1 Flash 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 条硬约束,
变更日志补一行。
2026-10-14 14:12:54 +08:00
DeepSeek V4.1 Flash 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 零警告。
2026-10-13 17:56:00 +08:00
DeepSeek V4.1 Flash 2b02518f85 样式区「调整」行改为 系数/镜像/绝对值:系数二级面板(还原图标 + 整行输入框)、镜像二级面板(水平/垂直复选框);Y 轴自适应按变换后曲线
- 「调整」行三个按钮改为「系数」「镜像」「绝对值」(顺序即结算顺序:系数 → 绝对值 → 镜像)。
  「系数」「镜像」点击后在按钮下沿展开二级面板(按下只登记、抬起命中同一按钮才展开,
  与自定义颜色入口一致),「绝对值」仍是直接切换的按钮。
  打开面板时主动清除样式区悬停态 —— 面板会接管鼠标事件,否则该按钮会一直保持"鼠标按住"般的高亮。

- 数据层:`SignalStyle::inverted` 拆成 `mirrorVertical`(Y=0 上下翻转)与
  `mirrorHorizontal`(水平镜像 = 数据反序);新增 `Mirrored()`、
  `SourceIndexFor`(水平镜像的遍历下标 → 源下标映射)、`TransformValueRange`(区间映射)。
  `ApplyDrawTransform` 顺序固定为 系数 → 绝对值 → 垂直镜像;水平镜像在采样遍历层处理。

- 绘制链路:`BuildPolyline`(逐点描边与像素列包络两条路径、跨列边界插值、粗粒度分组层)、
  派生预览曲线、光标取值读数(含 Δt)全部按同一变换取值;水平镜像时按源下标区间反序读取,
  包络层仍复用粗粒度分组;折线缓存键、`SceneContentHash`、样式快照都带上两个镜像位。

- 系数二级面板:标题「系数调整」,标题栏最右端是一个可点击的「还原」自绘图标按钮
  (悬停有高亮反馈,点击后系数重置为 1,面板保持打开);下面是一整行输入框
  (不再显示「绘制系数」说明文字),数值至少显示两位小数、精度更高时按实际精度显示
  (1 → 1.00、2.5 → 2.50、1.2345678 原样)。

- 镜像二级面板:两行带复选框的切换项「水平镜像」「垂直镜像」,点击后**面板保持打开**
  (`MenuItem::keepOpen`),勾选状态就地回写;任一项勾选时「镜像」按钮显示为激活态。

- Y 轴自适应修复:整段自适应(`TabConfig::ComputeLaneRange`)按区间映射(`TransformValueRange`)
  计算,不再直接用原始 min/max;可见区间自适应(`AutoRangeLaneToView`)逐点套用绘制变换
  (含水平镜像的源下标映射)。用户报告的"调整系数/镜像后自适应区域错误、仍按原始数据"由此修复。

- `PopupMenu`:新增 `MenuItemKind::HEADER_ACTION`(标题 + 右侧可点击图标,图标有悬停高亮)、
  `MenuItem::keepOpen`(切换项点击后不关闭菜单)、`HeaderIconRect()`,
  以及无说明文字的整行输入框(占满内容区)。

验证(离屏 harness,SW_HIDE、不动鼠标键盘屏幕):
- [AL] 重写/扩充:变换顺序与区间映射(含水平镜像的源下标反序)、调整行三按钮的顺序/等宽/标签不裁切、
  「系数」按住不弹出 / 移开取消 / 单击展开(标题 HEADER_ACTION、无标题输入框占满整行、初始 1.00、
  两位小数与更高精度回显、提交后面板保持打开、折线缓存键携带系数)、
  「还原」图标位置/悬停像素变化/点击后系数回到 1.00 且面板保持打开、
  「镜像」面板两行复选框(初始未勾选、keepOpen、勾选后按钮激活、取消其一保留另一)、
  打开面板后按钮不再停留高亮、系数/镜像按钮的激活填充与其它启用态切换按钮逐像素同色;
  `BuildPolyline` 顶点范围与变换后数据范围逐项对齐(默认/系数 2/绝对值/垂直镜像/三者叠加),
  水平镜像用窄视窗逐顶点核对"取的是对面端点的采样"(8/8)+ 读数跟随;
  以及 Y 轴自适应:整段/可见区间自适应在系数、绝对值、垂直镜像下均跟随变换后的曲线。
- 合计 594/594 通过;make clean && make all 零警告。
2026-10-13 14:50:38 +08:00
DeepSeek V4.1 Flash 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 零警告。
2026-10-13 12:06:38 +08:00
DeepSeek V4.1 Flash 59a84ba126 「弱峰调整」升级:三组识别规则 + 滑动条 ±400% 梯度驱动
一、三组识别规则(以波峰 k 为例;L/l = 左相邻波峰/波谷,r/R = 右相邻波谷/波峰;
   leftMean = (L+l)/2,rightMean = (r+R)/2)
- 第一类:v < leftMean 且 v < rightMean(两侧的"相邻峰+相邻谷"均值都要低于)
- 第二类:v < leftMean 或 v < rightMean(放宽为"或")
- 第三类:v < L 且 v < R(只与左右同类极值比较,最宽松)
波谷把"低于"换成"高于"、min/max 互换。三组不是包含关系(第三类不蕴含第二类),
因此确实存在"只满足第三类"的点。

二、梯度驱动(滑动条 |a| ∈ [0,400%];每组自身仍是 ±200%)
  0–100%   :只有第一类在动(0–100%)
  100–200% :第一类 100–200%,第二类开始 0–100%
  200–300% :第一类到上限,第二类 100–200%,第三类开始 0–100%
  300–400% :前两类到上限,第三类 100–200%
负值完全对称。实现:first = sign*min(|a|,2)、second = sign*clamp(|a|-1,0,2)、
third = sign*clamp(|a|-2,0,2);某点同时满足多组时取幅度绝对值最大的一组(= 最严格的那组)。

三、每组自身的目标值映射(±200%)
  0 – +100%  :当前值 → 左右同类极值中较温和的一侧(波峰取较低者、波谷取较高者)
  +100 – +200%:该侧 → 左右同类极值的均值(不再是"较大的那个")
  0 – -100%  :当前值 → 相邻异类极值中较近的一个(波峰取较高的波谷、波谷取较低的波峰)
  -100 – -200%:该侧 → 相邻异类极值的均值(不再是"最小的那个")
负幅度的"不得反向增强"夹取保持不变;相邻极值之间的 smoothstep 平滑偏移逐字未改。

四、界面
滑动条量程 -400% – +400%(0 在正中间,kWeakPeakSliderScale = 4.0);数值列按 "+400%" 取宽;
右侧数值始终带符号。

五、验证(全部离屏,不占用鼠标/键盘/屏幕)
- uitest 463/463:第一类点(弱峰 520、相邻峰 620/640)0/50/100/150/200% → 520/570/620/625/630;
  弱谷 580 → 580/540/500/500/500,负值 590/600/605/610;
  只满足第二类的峰 130:100% 不动、150/200/250/300/400% → 140/150/162.5/175/175;
  负值 -100% 不动、-150/-200/-250/-300% → 115/100/97.5/95;
  只满足第三类的峰 195:200% 不动、250/300/400% → 197.5/200/200;
  第一类点 200% 已达上限(400% 不再变化);±400% 夹取;默认 0%;滑块中间 = 0%、
  两端 = ±400%、轨道 3/4 处 ≈ +200%。
- [Y] 端到端:合成 ripple 弱峰区间 20 → 70(50% 幅度,健康 120)。
- panperf PASS;layercheck clobbers=0/24、pan frames wrong=0/4;cursorcheck 30/30;
  dragcheck 0 绘制错误;make clean && make all 零警告。
2026-10-12 19:38:46 +08:00
DeepSeek V4.1 Flash 14b60f5d9e 修复上下拖动后的绘制错误(图层条带 GDI 裁剪)+「弱峰调整」定义/默认值/符号显示
一、绘制错误真因:图层条带渲染时 GDI 网格线越界作画并烘进图层
上一轮把图层条带从"只有横向条"扩展到"横向条 + 纵向条"(纵向滚动补画)。但 PaintLane 里 GDI
(网格线/分界线)的裁剪矩形 RectF(m_area.X, clipTop, m_area.Width, clipBottom - clipTop) 只把 x
限制在条带内、y 仍是整个网格可见范围。纵向条带(全宽 × 窄 y)渲染时:
  * GDI 网格线被画到条带之外(那儿的像素由滚动而来、本来是正确的);
  * 那些行原本是"波形盖在网格线之上",变成"网格线盖在波形之上",与正确渲染不一致;
  * 错误像素就在图层位图里,会被后续每次平移带着移动 → "拖到哪儿都有绘制错误"。
修法:把"本次允许改动的区域"算成 laneClip = IntersectRect(m_area, 网格可见矩形),同时用于
Graphics::SetClip 与 GDI 的 gridClip(横向补条本来就没问题,所以此前只有纵向/斜向暴露)。

二、新增探针 .preflight/dragcheck(复现用户手势)
注入真实中键消息:先把最上方网格向上拖半个网格高(触发纵向滚动),再保持按住拖左右/上下/斜向、
最后拖出窗口;每帧把图层画布的有效条带(marginY + vOffset 起、网格高行)与"用同一 wideMin 新渲染
的画布"逐像素比较(滚动是纯整数平移,必须逐像素相同;用 FromHBITMAP + LockBits 直接读像素)。
修复前:被拖网格整行不一致(2410 px ≈ 2 行全宽,错误行固定在 canvas 行 138 = 某条横向网格线,
直接指向 GDI 网格线);修复后:最差 ≤171 px(仅补条边界的抗锯齿边缘),0 处绘制错误。

三、「弱峰调整」定义
"是否偏弱"改为(正/负幅度一致):波峰必须同时低于左右两侧各自的「相邻波峰 + 相邻波谷」均值;
波谷必须同时高于上述两个均值。与旧实现(只与左右两个同类极值比较)不同:整段均匀偏弱的纹波
不再被判为弱峰,因此合成测试波形重设计(分段线性、谷恒 500、弱峰 520、健康峰 620/640)。

四、默认幅度与符号显示
默认幅度改为 0%(面板提出即不改动波形);滑块右侧数值始终显示符号(+150% / -150% / 0%),
数值列按 "+200%" 取宽避免跳动。

五、验证(全部离屏,不占用鼠标/键盘/屏幕)
- uitest 441/441:[X] 正幅度 0/50/100/150/200% → 峰 520/570/620/630/640、弱谷 580/540/500/500/500;
  负幅度 -50/-100/-200% → 峰 510/500/500、谷 590/600/620;超范围夹取;"未反向增强"(极值点);
  健康段逐位不变;30% 幅度下 N=1/2/3/10 → 550/571/571/571(收敛);默认 0%;滑块中间 = 0%、
  两端 = ±200%;数值带符号(+150% 与 -150% 墨迹等宽、都比 0% 宽 ≥8px)。
- [Y] 端到端:合成 ripple(谷恒 500、一个弱峰 520)弱峰区间 20 → 70(健康 120)。
- dragcheck PASS(0 绘制错误);panperf 竖直中位 5.1ms、水平 7.1ms、20 面板竖直 25.0ms / 水平 41.3ms;
  layercheck clobbers=0/24、pan frames wrong=0/4;cursorcheck 30/30;make clean && make all 零警告。
2026-10-12 18:25:31 +08:00
DeepSeek V4.1 Flash 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 零警告。
2026-10-12 17:10:51 +08:00
DeepSeek V4.1 Flash a30a6fcf4d 弱峰增强改回逐信号滑块;迭代次数上限可就地编辑
一、参数输入方式(回到滑块形态,但每个信号各有一套)
- 面板布局:每个被勾选的信号 = 名称行(+复选框)+ 两行滑块:
  「增强等级 %」(0~200%)与「迭代次数 N」(1 ~ 该信号自己的上限)。
  EdgePanelLayout 新增逐信号矩形 rowLevelTracks/rowLevelValues/rowIterTracks/rowIterValues;
  PEAK 的 rowPoints/rowIterations 恒为空(MEAN/CENTER 仍用文本输入框)。
- 恢复 kWeakPeakSliderRowHeight/kWeakPeakTrackHeight/kWeakPeakTrackMinWidth 与
  WeakPeakSliderHit();恢复 PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS,
  并新增 m_peakDragRef(拖动时必须知道是哪个信号)。
- 列宽算式集中在 WeakPeakLabelColumnWidth()/WeakPeakValueColumnWidth(),
  与 EdgePanelWidth 共用,避免布局与面板宽度计算漂移。

二、迭代次数滑块上限可就地编辑(用户要求的行为)
- 点击「迭代次数 N」滑块右侧的数值文本 → 整条滑块行(轨道 + 数值)变成一个输入框;
  左侧标签保留可见。
- 输入框以空内容开始;空内容时以灰色提示当前上限值(m_peakEditCurrentMax,EndEdit 清零)。
- 过滤器为 EditFilter::BYTE(纯数字):负号、小数点、字母无法键入,粘贴同样被清洗,
  从源头满足"只允许输入正整数"。
- 提交(回车 / 点击别处):仅当解析成功且 1 <= 值 <= 1000 时才作为新上限;
  空内容、0、超范围一律保持原上限不变;生效后若当前迭代次数超过新上限则自动替换为新上限。
- 输入框在 paintOverlays() 中与 Y 轴上下限输入框一起绘制(覆盖在面板之上)。
- 新增 PlotEditTarget::PEAK_ITERATIONS_MAX;删除 PEAK_LEVEL_PERCENT/PEAK_ITERATIONS
  (等级只能拖滑块,迭代次数只能拖滑块)。

三、数据与缓存
- Lane::WeakPeakItem 新增 maxIterations(默认 10)。
- maxIterations 参与 PanelSlotKey(决定圆钮位置,面板必须重画),
  但不参与 SceneContentHash(不影响网格内曲线,不应触发波形重画)。

四、测试
- uitest [X] 改为:滑块存在性/层序/列宽、等级滑块拖动(中间=100%、右端=200%)、
  迭代滑块拖动(左端=1、右端=当前上限)、点击数值进入上限编辑(空内容 + 提示值 + 输入框
  横跨整行、空内容回车不改、'-'/字母被拒、输入 40 生效且右端变 40、下调到 4 时当前值被夹到 4、
  输入 0 被拒、点击别处提交),并保存编辑态截图供目视核对。
- make all 零警告;uitest 409/409 通过;panperf PASS;layercheck 保持像素一致。
2026-10-12 13:35:50 +08:00
DeepSeek V4.1 Flash 1ddbc37b78 平移不再阻塞前台刷新;弱峰增强逐信号参数;浮动面板加入层序
一、平移:前台刷新线程永不阻塞(解决"按住键拖拽时质量下降/断线/周期性卡顿")
- RollWideBuffer 不再整块重画:逐网格整数像素平移 + 只补细条;内容指纹过期或锚点离视界
  过远的网格只登记到 m_layerRebuildPending 并仍按现有像素平移(位置照样跟随视界)。
- 新增 RenderPendingLaneRebuilds(budget):Paint 每帧最多重画 1 个网格,其余留到后续帧;
  队列非空时 PostMessage(kMessagePlotDerivedReady) 请宿主再送一帧,直到补齐。
- BlitWideBuffer 改为局部贴图:把 BitBlt 夹到图层可用区间,图层没覆盖到的部分留空由底色
  填充(旧实现直接跳过整块,导致网格瞬间变空 = "断线")。
- "结算"(整幅全质量精确帧)触发条件收紧为三者同时成立:内容与视界同上一条快路径帧、
  m_dragMode == NONE、全部图层就绪。拖拽期间不再结算,消除"宽基底快路径 / 整幅重绘(关AA)"
  每两帧振荡造成的质量下降与周期性卡顿。
- 新增 LaneLayerReady(i) / LanesAllReadyForView();原 LayersReadyForView() 更名
  LayersStructurallyReady()(只看结构,用于"跨度/布局变化必须整批重建一次")。
- 实测(.preflight/panperf,VERY_BIG_DATA 5 网格深放大,每帧平移 60px):平移 40 帧
  中位 6.2ms / p90 7.2ms / max 7.6ms;按住键且视界停在边界 12 帧中位 0.29ms(无整幅重绘)。

二、弱峰增强:改为逐信号参数(对齐均值滤波)
- Lane::WeakPeakItem 由 using = MeanFilterItem 改为独立结构体
  { ref, levelPercent(0-200, 默认 150), iterations(1-10, 默认 1) };删除网格级
  Lane::weakPeakLevel / weakPeakIterations。
- 面板改为与均值滤波完全同构:每个被勾选信号一行"名称 + 复选框",第二排两个输入框
  (增强等级 % / 迭代次数 N),底部"构建为新信号";删除网格级两个滑块及其常量、
  WeakPeakSliderHit、PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS、m_peakDragLane、
  EdgePanelLayout::slider/sliderIterations。
- 新增 PlotEditTarget::PEAK_LEVEL_PERCENT / PEAK_ITERATIONS,EndEdit 中分别夹到 0-200 / 1-10。
- 指纹/快照/预览曲线/光标胶囊/构建新信号全部改为逐信号参数(同一网格内不同信号可用不同强度,
  且"预览"与"构建"逐位一致);WeakPeakItem 不再是 MeanFilterItem,purgeRefs 改为泛型 lambda。

三、浮动面板:层序(z-order)与命中修复
- 新增 m_panelOrder(队尾 = 最上层):SyncPanelOrder 保留已有序、新面板追加到最上层;
  TopPanelAtPoint 从最上层往下取第一个命中;BringPanelToFront 在点击面板时把它置顶。
- 绘制改为按 m_panelOrder 自下而上;PanelGlobalKey 混合层序(层序变化即整层重画);
  QueryCursor / OnMouseMove 的"是否在面板上"也走 TopPanelAtPoint。
- 修复"上层面板的 ✕ / 标题栏被下层参数输入框抢走,导致上层面板关不掉/拖不动"。

四、测试
- uitest 新增 [AG]:层预算(每帧只重画 1 个网格、剩余排队、最终补齐并全部就绪)、
  局部贴图(只覆盖视界一半时左侧被贴、右侧保持哨兵色)、面板层序(上层 ✕ 命中上层、
  下层不受影响、点击下层置顶、TopPanelAtPoint 随之改变、新开面板在最上层)。
- [X] 改为逐信号参数(含两个输入框就地编辑:写入该信号自己的条目、越界夹取),
  移除已不存在的滑块断言。
- 新增离屏探针 .preflight/panperf(平移帧耗时 + 按住键静止帧耗时)。
- make all 零警告;uitest 388/388 通过;panperf PASS;layercheck 保持像素一致。
2026-10-12 12:06:42 +08:00
DeepSeek V4.1 Flash 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)。
2026-10-12 10:10:06 +08:00
DeepSeek V4.1 Flash 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`(离屏探针 + 两套微基准),已随任务清理。
2026-10-10 20:13:21 +08:00
DeepSeek V4.1 Flash 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%+ 采样被调整)。
2026-10-10 19:20:09 +08:00
DeepSeek V4.1 Flash 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 不变)、
  光标拖拽始终落在可见区间内。
2026-10-10 18:46:01 +08:00
DeepSeek V4.1 Flash d4206a8d67 实验性功能新增「差异放大」,并修正「构建为新信号」按钮的间距
1. 新增「实验性功能 → 差异放大」(与其它实验功能一样是带勾选的开关)
   - 用途:纹波类信号某个周期(甚至半个周期)的 M 形下凹明显偏弱时,
     把这段下凹按比例拉回周边振幅,恢复正常周期的对比度,便于看出波形异常。
   - 算法(全 O(n),不额外占用与信号长度成正比以外的内存):
     以半径约半个纹波周期的滑动平均作为纹波中心线,用邻域内「交流分量的最大值」
     作为局部纹波振幅,取全信号振幅的 75 分位作为目标振幅,
     逐点增益 = 目标振幅 / 局部振幅(夹在 1 到放大倍数之间),
     输出 = 中心线 + 增益 × 交流分量,再夹在周边包络内。
   - 关键性质(均已用合成正弦逐点验证):
     * 增益下界恒为 1,因此**只有被判定为"偏弱"的采样点会改变**,
       其余采样点输出与原值逐位相同 —— 实测逐周期表格中只有一个周期发生变化;
     * 放大结果夹在周边包络内,因此**任何一点都不超出原有的振幅范围**,
       整体值域与其它周期的峰峰值都不变;
     * 端点自动收敛到不放大,避免记录首尾被整片抬升。
   - 参数「窗口点数 X」(窗口长度,建议略大于纹波周期的一半)与
     「放大倍数 N」(增益上限)都可在浮动面板内按信号编辑。

2. 浮动面板
   - 与「均值滤波」完全同一套排版:自上而下逐信号条目,
     第一排信号名与复选框、第二排两个参数输入框;取消勾选即不显示该信号的参数与预览曲线;
     启用时默认只勾选网格内第一个可见信号。
   - 新增「构建为新信号」按钮,按各信号自己的参数把结果插入到源信号下方,
     与「均值滤波」的构建机制完全一致(统一走同一套请求与插入流程)。

3. 光标交互
   - 纵向光标额外标注差异放大曲线在该时刻的取值,配色与原始信号一致,
     数值尾部括号内容为「差异化」,双光标时同样带增量。

4. 修复「构建为新信号」按钮与面板下边界的间距
   - 原按钮行直接贴着面板下边框,与面板左右内边距及内容间距都不协调;
     现在按钮上方与下方各保留一个与左右一致的内边距(均值滤波与差异放大同时生效)。

测试:离屏验证套件新增两组检查(合成正弦上算法只改动一个周期、放大不越界、
参数上限生效、面板排版与按钮间距;再写一份合成纹波文件真正加载渲染并核对曲线数值),
合计 289 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
2026-10-10 17:54:27 +08:00
DeepSeek V4.1 Flash 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 项检查全部通过;验证过程不显示窗口、不移动鼠标键盘、不抓屏。
2026-10-10 16:47:05 +08:00
DeepSeek V4.1 Flash bc8db72f18 修复色域拖拽回弹、网格 GX 拖拽重排、均值滤波多阶与逐信号条目
1. 自定义颜色面板:拖动时鼠标移出控件不再"弹回去"
   - 根因是选点位置每帧都从 RGB 反推:色相 1 与 0 是同一个颜色(红),拖到色相条底部
     下一帧反推回 0,指示条弹到顶部;拖到色域方块右下角之外时明度为 0(黑色),
     黑色的饱和度无定义,反推得 0,选点弹到最左边。
   - 现在把"选点位置(饱和度/明度)与色相"作为独立状态保存,只由对应控件与外部改色维护;
     越界一律夹取到最近边界,因此鼠标离开控件后选点停在边界上。
   - 统一色域方块、色相条、预览色块与色号框的圆角;RGB 滑块适当加粗;
     色域选点去掉外圈深色描边(只保留白色描边),色相指示器改为白色描边的圆角长方条、
     底色取该位置的颜色;色相条改用分段线性渐变绘制。

2. 网格 GX 编号支持拖拽重排
   - 按住网格图例左侧的 GX 徽标即可上下拖动,整块网格(连同波形与标尺)跟随鼠标移动;
     途经的网格保持自身大小、整体让出一格,目标槽位有吸附高亮提示。
   - 松开后真正重排并从下到上/从上到下重新编号,信号列表徽标、图例徽标、菜单与浮动面板
     中所有 GX 编号同步更新;手动排布后切换为自定义划分,后续重建网格不会打乱次序。
   - 拖动期间复用静态场景缓存,按网格条带平移贴图,因此大文件下依然流畅。

3. 均值滤波升级为"均值点数 X + 阶数 N"
   - 原"均值点数 N"更名"均值点数 X",新增"阶数 N":预览曲线以 X 点均值滤波的结果为蓝本,
     再重复同样的滤波共 N 阶(1 阶即与原来的单次滤波逐位一致)。
   - 浮动面板改为逐信号条目:第一排是信号名与复选框,第二排是均值点数与阶数输入;
     只有勾选的信号才绘制预览曲线,未勾选时不显示第二排;启用功能时默认只勾选第一个信号。
   - 参数按信号保存(同一网格内不同信号可用不同参数),「构建为新信号」按各自参数构建。
2026-10-10 15:33:45 +08:00
DeepSeek V4.1 Flash 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,保证缓冲区内即刻析构。
2026-10-10 10:54:50 +08:00
DeepSeek V4.1 Flash 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 无任何警告。
2026-10-09 21:52:46 +08:00
DeepSeek V4.1 Flash f5028b1e18 实验性功能新增「均值滤波」+ 构建为新信号
- 「实验性功能」二级菜单新增带勾选的「均值滤波」:开启后为本网格每条信号叠加绘制
  一条 N 点滑动平均曲线,算法与「边沿计数(中心轴)」的中心平均值曲线**完全一致**
  (同一实现 ComputeMovingAverage),配色同样由信号色派生为高对比度颜色、透明度一致。
- 新增均值滤波浮动面板:标题栏可拖拽、✕ 关闭,面板内列出被处理的信号名、
  「均值点数 N」内联编辑行,以及底部的「构建为新信号」按钮(悬停高亮 + 手型光标)。
- 「构建为新信号」:把滤波后的曲线作为新信号插入,命名为「<原信号名称> (N点均值)」,
  归属到原信号所属组的原信号位置**下方**,成为独立的新信号;其所在网格也移动到
  原信号网格的下方。合并网格时一次按钮为该网格内每条可见信号各构建一条。
  为此新增 DataSet::InsertDerivedSignal / TabConfig::RemapAfterSignalInsert /
  TabConfig::MoveLaneOfSignalBelow / TabConfig::RenameLanes(插入位置之后的信号引用
  与可见性列同步重映射)。
- 光标交互与「边沿计数(中心轴)」同构:光标处标注均值滤波曲线的取值
  与双光标增量,文案为「值(均值滤波)」/「值(+增量 均值滤波)」;
  两条派生曲线的取值胶囊已抽成同一段绘制逻辑。
- 浮动面板种类由 bool centerAxis 改为 EdgePanelKind{COUNT,CENTER,MEAN},
  布局/宽度/命中测试/绘制统一走同一套几何。
- 以 VERY_BIG_DATA.CSV 验证:曲线预览、N 编辑、双光标取值标注、
  单信号与合并网格两种情况下的新信号命名与插入位置均正确。
2026-10-09 21:00:38 +08:00
DeepSeek V4.1 Flash 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 无警告无错误。
2026-10-09 15:21:10 +08:00
DeepSeek V4.1 Flash 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 无警告无错误。
2026-10-09 11:23:15 +08:00
DeepSeek V4.1 Flash 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 无警告无错误。
- 修复过程中发现:静态场景缓存命中路径(贴图快路径)与整幅重绘路径是两处独立的
  叠加层绘制入口,新增的随鼠标变化内容必须同时加在两边。
2026-10-09 09:30:30 +08:00
DeepSeek V4.1 Flash 388d3b6441 feat(ui): 修复光标放大后丢失、选区放大合并与右键放弃、双光标与数据点取值标注
1) 打开「选区 Y 轴自适应」时立即自适应一次
- 之前打开开关(单个网格的菜单项或全局项/工具栏按钮)不会真的改变量程,
  只有当用户再次做时间轴选区放大时才生效,观感上就是"点了没反应"
- ToggleLaneAutoFitToSelection / SetAllLanesAutoFitToSelection(enable, applyNow=true)
  在打开时按当前视图立刻执行一次 AutoRangeLaneToView
- 新增 ApplyAutoFitToSelectionNow / ApplyAllAutoFitToSelectionNow

2) 「按网格时间轴展开」同时对该网格做一次 Y 轴自适应
- FitTimeToLane 在把时间轴展开到本网格信号范围后调用 AutoRangeLaneToView,
  避免出现"时间对了但幅值区间不合适"的波形

3) 修复时间光标在大幅放大后丢失、无法拖动的问题
- 根因:缩放/平移改变 ViewMin/ViewMax 后,光标时间可能落到新视野之外,
  PaintCursor 直接 return → 光标消失,用户报告为"放大到 10ms 一格后拖动光标会直接丢失"
- 新增 ClampCursorToView(),在 ApplyTimeSelection / FitAll / FitTimeToLane /
  平移(PAN) 后把两条光标夹回可见区间;CURSOR_MOVE 也把目标时间夹在 [ViewMin, ViewMax]
- IsOnCursorLine 改为输出命中的光标序号,为双光标拖动做准备

4) 工具栏「时间轴选区」+「Y 轴选区」合并为「选区放大」,并支持右键放弃选区
- 单一 toggle(ToolbarAction::ZOOM_SELECT / PlotZoomMode::SELECT);
  按下后按首次明显位移方向决定轴向:横向=时间轴选区,纵向=该网格 Y 轴选区,
  轴向固定不变,轴向未定时不画选区(避免闪烁)
- 框选过程中按右键 = 放弃当前选区并显示提示浮层;与 Esc 不同,**不退出**选区放大模式
- 为此 WM_RBUTTONUP 在左键仍按下时不 ReleaseCapture / 不清 m_activeTarget,
  否则松左键收不到 OnLButtonUp,m_dragMode 会永远停在 RUBBER_SELECT
- 新增 PlotView 内的提示浮层(m_hintText + PaintHint):切换模式/放弃选区时显示一行说明,
  在网格内再次移动鼠标自动清除,无需定时器
- Esc 语义保持原样:放弃拖拽 + 退出选区放大模式

5) 光标功能改为三态:常态 → 单光标 → 双光标 → 常态
- TabConfig 用 PlotCursorMode{NONE,SINGLE,DOUBLE} + CursorTime/CursorTimeAlt 两个时间;
  CursorEnabled() 变成只读派生;ToggleCursor 删除,改为 CycleCursorMode()
- 单 → 双时第二条光标落在第一条附近(+12% 视图跨度,越界反向)并保证在可见范围内
- 两条光标颜色区分且同族:光标 1 = Solarized 橙 #CB4B16(theme.cursor),
  光标 2 = 青 #2AA198(新增 theme.cursorAlt),光标 2 的顶部把手画成朝上的三角
- 时间读数从绘图区顶部移到**底部**,单/双光标都始终显示(不必悬停才可见)

6) 取消原有的光标数值读数框,改为直接在数据点位置标注取值
- 删除 PaintLaneReadout/PaintLaneReadouts(网格右上角的读数框)
- PaintCursor 在光标与每条可见信号曲线的交点处绘制信号色圆角胶囊(底色=信号色,
  文字按 IsDarkColor 自动取深/浅色),一条信号一个标注,颜色与信号一一对应;
  右侧空间不足时自动标到左侧
- 双光标时以时间较小者为左;**右侧光标**显示"当前值 , ±增量"(增量 = 右 − 左),
  增量为负时用负号前缀,避免出现 "±-" 双符号

验证([1].CSV 11 信号 / VERY_BIG_DATA 1.77GB,窗口 1725x996,125% DPI):
- 三态循环:点 1 次出现单光标、2 次双光标、3 次回到无光标
- 双光标读数:左"426.316 / 427.749",右"538.583 , ±112.267 / 513.726 , ±85.9769"
- 时间读数在底部显示(橙 0.63 / 青 0.7812);第二条光标出现在第一条右侧同一视野内
- 超大文件:放大两次后开启光标仍然可见,拖动从 68.2343 变为 68.1911(原先直接消失)
- 「Y 轴自适应」在放大后的视图中点击立即把量程收到可见区间(300~520)
- 「选区 Y 轴自适应」菜单项点击后立即生效并被勾选,全局项同步勾选
- 「按网格时间轴展开」把时间轴展开回全范围并重新自适应 Y 轴
- 选区放大:横向拖拽只放大时间轴、纵向拖拽只放大 Y 轴;右键放弃后视图不变、提示出现、
  模式仍保持选中;Esc 则同时放弃并退出模式
- 回归:空闲 CPU 0.00%;make clean; make all 无警告无错误
2026-10-08 16:58:55 +08:00
DeepSeek V4.1 Flash 0fffb3ed09 feat(ui): 修复预设色块误跳自定义色、选区 Y 轴自适应改为切换开关、菜单项改名移位
1) 修复样式区"点预设色块直接跳到自定义色块"
- 根因:加载时 customColor 被赋成信号自身的预设色,而样式区按
  "color == customColor 即视为在用自定义色" 判定高亮,于是点自身的预设色时
  高亮立刻跳到自定义色块上(第 N 个信号点第 N 个预设色必然复现)
- customColor 初值改为调色板中"本信号预设色向后递进一格"的颜色
  (palette[(slot + 1) % count]),自定义色块因此显示与预设色块排布连续的下一色
- 同时收紧高亮判定:仅当当前颜色"不属于任何可见预设色"时才认为在用自定义色。
  这样即使自定义色恰好等于某个预设色,点击该预设色也始终高亮预设色块,
  彻底消除"高亮乱跳";用拾色器选出的非预设色仍正常高亮自定义色块

2)「选区 Y 轴自适应」改为切换开关(原为一次性操作)
- Lane 用单一布尔量 fitYToSelection 表示(删除 viewAutoRange/viewAutoMin/viewAutoMax)
- 开启后每次时间轴选区放大(ApplyTimeSelection → ApplySelectionAutoFit)
  都会把该网格 Y 轴匹配到新的可见时间区间
- 任何手动幅值调整(Y 轴选区放大、纵向平移、缩放、直接输入上下限)都会关闭对应网格的开关
- RebuildLanes 按信号继承该开关:勾选/取消勾选信号不再把用户刚打开的开关悄悄清掉

3) 菜单「恢复 Y 轴自适应」移除,原位置新增「选区 Y 轴自适应(全局)」
- 全局项为切换状态:选中即把全部网格的开关置为开,取消即全部置为关;
  任意网格单独关掉后,全局项立即变为未勾选(= AllLanesAutoFitToSelection)
- 工具栏在「适应视图」之后新增「Y 轴自适应」切换按钮,与全局项是同一个开关,
  每帧同步选中态
- 删除了 PLOT_CMD_LANE_AUTO / PlotView::LaneRangeIsAuto / ResetLaneRange 等已无用的接口

4)「时间轴自适应(匹配本网格)」改名为「按网格时间轴展开」并移到分隔线上方
- 新顺序:网格 N / Y 轴最大值 / Y 轴最小值 / 选区 Y 轴自适应 / 按网格时间轴展开 /
  --- / 选区 Y 轴自适应(全局)/ 全部自适应

验证(TestData/[1].CSV 11 信号,窗口 1725x996,125% DPI):
- 颜色:初始高亮在预设色块 1;点预设 1 与预设 2(等于自定义色初值)均保持点中的色块高亮;
  用拾色器改成非预设色后高亮正确落在自定义色块
- 逐网格:菜单项勾选/取消即时反映;开启的网格做时间轴选区放大后 Y 轴自动匹配
  (仅该网格变化,其它网格保持整段范围)
- 全局:工具栏按钮与菜单全局项互相联动;单独取消一个网格后两者都变为未勾选;
  隐藏/显示信号后开关保持
- 「按网格时间轴展开」能把缩放后的时间轴展开回该网格信号范围;「全部自适应」仍正常
- 超大文件回归(VERY_BIG_DATA 1.77GB)菜单/悬停正常;空闲 CPU 0.00%

构建: make clean; make all 无警告无错误
2026-10-07 13:57:46 +08:00
DeepSeek V4.1 Flash dd48cdd2e1 feat(ui): 工具栏"选区"改名、菜单对勾右置与边距修正、选区自适应勾选、悬停/选区拖拽性能
1) 工具栏文案:"时间轴缩放"/"Y 轴缩放" -> "时间轴选区"/"Y 轴选区"
- Toolbar.cpp 按钮标签、Toolbar.hpp 枚举注释、Application 状态提示语同步更新

2) 网格右键菜单:对勾从左侧移到右侧,并修正菜单内各元素边距
- 文本列统一为 kPadH + kItemPad(5 + 8),三类条目(HEADER/NORMAL/EDIT)共用;
  原先左侧预留 22px 标记列,导致"网格 N"、"Y 轴最大值"、"选区 Y 轴自适应"等
  所有文本的左边距被整体推右(用户报告的"文本左边距过大")
- 对勾改画在条目右侧的 kMarkerColumn(18) 列内,文本宽度相应预留,不会与文字重叠
- EDIT 行输入框改为右对齐到条目内容区右边界,宽度由 NaturalEditFieldWidth() 统一计算,
  空间不足时为左侧说明文字保留自然宽度(避免被菜单宽度上限挤成省略号)
- 分隔线、色块行同样改用 kItemPad 内缩;kRightPad 已删除

3) "选区 Y 轴自适应"增加与"Y 轴自适应"一致的勾选标记
- Lane 新增 viewAutoRange / viewAutoMin / viewAutoMax,记录应用该操作时的视图时间区间
- 新增 PlotView::LaneRangeIsAutoToView():只有未被任何手动幅值改动且时间选区未变化时才为 true
- 所有手动改动路径(ApplyValueSelection/ScaleLaneRange/ShiftLaneRange/ApplyLaneValueText/
  EndEdit/ResetLaneRange/MoveSignalToLane)都会清除该状态;
  时间选区变化后 viewAutoMin/Max 与当前视图不符,勾选自动消失
- RefreshPlotMenuValues() 同步刷新该项状态

4) 修复大文件(VERY_BIG_DATA 1.77GB / 5 信号)下悬停与选区缩放的严重卡顿
- 根因:任何悬停态变化(左侧信号列表、网格内命中曲线提示)或选区拖拽都会触发整幅重绘,
  其中波形绘制约 80ms/帧,因此交互帧率只有 ~10fps
- 静态场景缓存从"仅拖拽信号/光标"推广为"只要场景键一致就复用",缓存区域由整个客户区
  收敛为仅绘图区:工具栏/标签栏/面板各自带悬停态必须每帧绘制(约 10.5ms),
  而真正昂贵的波形改为直接贴图(约 1ms)
- 场景键新增 contentHash(FNV-1a 折叠网格几何/幅值、每条信号的引用/可见性/
  颜色/透明度/线宽/样式/显示名与所属文件名):改样式、重命名、增删信号都会自动失效
- 随鼠标变化的内容全部移出缓存:新增 PaintHoverHighlight() 在贴图之后绘制
  悬停网格底色(半透明叠加)与悬停信号光晕(PaintSignalHalo());
  PaintLane 内只绘制拖拽中的信号光晕(key 中记录 haloFile/haloSignal)
- EnsureSceneCache 改为要求位图尺寸与当前区域严格相等(原 >= 复用会在窗口缩小后
  把旧像素画到绘图区之外)
- Application::Render 移除 reuseScene 分支,统一每帧绘制界面框架

实测(VERY_BIG_DATA 5 信号,Release):
- 整幅重绘 ~80ms -> 命中缓存贴图 ~1ms
- 悬停左侧信号列表:~95ms -> ~14ms/帧
- 悬停网格内信号(含光晕高亮):~95ms -> ~28~32ms/帧
- 时间轴 / Y 轴选区拖拽:~95~105ms -> ~19~22ms/帧
- 非拖拽静止帧 ~118ms 维持不变;空闲 CPU 0.00%

构建: make clean; make all 无警告无错误
2026-10-07 11:26:43 +08:00
DeepSeek V4.1 Flash a3e638fa58 fix(plot): 默认线宽 1.0px、菜单文本左对齐、散点白边修复与大文件拖拽性能优化
1) 默认线宽改为 1.0px
- SignalStyle::lineWidth 初值 1.6f -> 1.0f (DataModel.hpp / DataModel.cpp)
- 新增 LaneScroll() const 重载,供 const 的 MakeSceneCacheKey() 读取

2) 网格右键菜单文本左对齐
- PopupMenu 的 HEADER / NORMAL / EDIT 三类条目统一使用 kMarkerColumn(22.0f)
  作为文本列,替换原先写死的 8.0f,使 "网格 N"、"Y 轴最大值"、
  "Y 轴最小值"、"选区 Y 轴自适应" 等所有条目左边缘严格对齐

3) 修复散点/点线模式下数据点重叠处的白色缝隙
- 根因: GDI+ GraphicsPath 默认 FillModeAlternate(奇偶填充),重叠圆的交叠区域
  被反向填充,视觉上表现为白色缺口/白边
- 主绘制与拖拽 ghost 的 GraphicsPath 均显式 SetFillMode(FillModeWinding)
- ghost 由逐点 FillEllipse 改为单个 GraphicsPath 一次 FillPath(批量绘制)

4) 大文件拖拽流畅性优化 (VERY_BIG_DATA.CSV 1.77GB / 5 信号)
- 新增拖拽静态场景缓存: 在 MOVE_SIGNAL / CURSOR_MOVE 期间把整个客户区
  (绘图区 + 标签页 + 工具栏 + 信号面板) 捕快到 m_sceneDc 位图,后续帧直接
  BitBlt,仅在 SceneCacheKey(dragMode/viewMin/viewMax/laneScroll/laneCount/
  区域/scale) 变化时重绘。Application::Render 在复用场景时跳过窗口背景与
  各子控件绘制,缓存位图由 ~PlotView() 释放
- 失效点: SetContext / SetScale / Layout / InvalidatePolylineCache
- 光标数值读数从 PaintLane 抽出为 PaintLaneReadouts(),在缓存 blit 之后绘制,
  避免读数被缓存导致拖动时显示旧值
- 拖拽 ghost 复用已有 PolylineCacheEntry,改用 TranslateTransform 偏移,
  不再重复构造折线顶点
- 实测: 拖拽帧耗时 134-146ms -> 22-25ms;散点模式拖拽约 24.6ms;
  非拖拽重绘维持约 118ms;空闲 CPU 0.00%

构建: make clean; make all 无警告无错误
2026-10-07 10:22:57 +08:00
DeepSeek V4.1 Flash 04e98ee041 Esc 退出缩放模式、修复拖拽时波形变模糊、加速超大文件的模式切换
Esc 行为(用户要求):
- 网格内按 Esc 在取消拖拽选区的同时,退出全部缩放模式(时间轴缩放 / Y 轴缩放),
  工具栏两个缩放按钮随之回到未选中态;时间光标按钮不受影响。
- 新增 PlotView::ExitZoomModes(),由 Application 的 VK_ESCAPE 分支与 CancelDrag 一起调用。

修复拖拽时波形变模糊(用户报告):
- 上一轮为提升拖拽帧率引入了"交互降级":拖拽期间关闭抗锯齿 + 顶点预算减半 +
  每列只扫 32 个采样点。实测这会让包络丢掉峰值、线条失去抗锯齿,
  在选区放大 / 拖拽信号 / 拖拽光标时明显变"糊"。现已**全部移除**,
  任何交互期间都按全质量绘制。
- 用"粗粒度包络层"替代降级来保证交互帧率:
  SignalData 新增 coarseMin/coarseMax/coarseStride(每 256 个采样一组存 min/max,
  额外内存约为采样数据的 0.8%),在 BuildDataFile 统计 min/max 的同一个循环里顺带构建
  (不额外扫数据)。折线包络改为读取"每列覆盖到的分组",
  数据读取量从 O(可见采样数) 降到 O(像素列数 × 少量分组),**绘制结果与全量扫描完全一致**。
- 实测(VERY_BIG_DATA.CSV = 1.77GB、2580 万行 × 5 信号,全选显示):
  视图变化时的包络构建由 176~196ms 降到 ≈0ms;平移拖拽整帧由约 336ms 降到 90~120ms。
- 注意:包络路径按分组判断无效值(整组无有效值才断线),
  因此密集视图中孤立的 NaN 不再把波形切成两段(原先会),视觉上更干净。

加速超大文件的显示模式切换(用户报告):
- 散点绘制由逐点 FillEllipse 改为把全部散点合并进一条 GraphicsPath 后一次 FillPath:
  实测 2500 点由 63ms 降到 7ms(基准),程序内单条密集信号由约 51ms 降到约 22ms,
  仍是抗锯齿圆形,观感与逐点绘制一致。
- 线段/散点/点线切换时不再有卡顿感;空闲 CPU 仍为 0.00%。

验证:
- Esc:分别启用时间轴缩放 / Y 轴缩放后按 Esc,两个按钮均回到未选中(截图对比),
  时间光标按钮保持选中;框选拖拽中按 Esc 时间轴保持 0.2~1.2 不变。
- 清晰度:光标拖拽中与静止时对同一区域做像素哈希,结果完全一致;
  框选拖拽同样一致;信号拖拽中间态截图中源波形与虚影均为抗锯齿全质量。
- 超大文件:线段/散点/点线切换与拖拽的正常渲染(截图),1.77GB 文件加载过渡动画正常。
- 回归:菜单内联编辑 Y 轴上下限、选区 Y 轴自适应、图例点击选中、面板改名均正常。
- 内存:BIG_DATA 全选 122MB(粗粒度层约 +1MB);VERY_BIG 全选 1224MB(理论下限 1.25GB)。
- make clean && make all 零警告零错误。
2026-09-30 19:34:52 +08:00
DeepSeek V4.1 Flash 45b44eae57 网格菜单重构、大文件异步加载与内存/解析优化、修复应用图标
网格右键菜单:
- 菜单顺序改为: 网格 N / Y 轴最大值 / Y 轴最小值 / 选区 Y 轴自适应 / --分隔线-- /
  Y 轴自适应 / 时间轴自适应 / 全部自适应
  (恢复自适应移到分隔线以下、时间轴自适应上方)
- Y 轴最大值/最小值改为菜单内联输入框:点击就地编辑、回车提交,菜单不关闭,
  处理方式与拾色器的 RGB 内联编辑一致;提交后按真实夹取结果回写菜单显示值,
  并同步刷新自适应项的文案/对勾/可用状态
- 新增"选区 Y 轴自适应":只按当前显示时间段内的数据做幅值自适应
  (时间单调时二分定位区间,非单调时全扫并按区间过滤)

大文件加载:
- 加载改为工作线程执行(解析 + 按列重组),主线程抽送消息并播放过渡动画
  (旋转弧线 + 进度条 + 文件名 + 百分比 + 按 Esc 取消),界面不再整块卡死
- 加载完成后主线程只做一次 O(1) 的移动入队,不会产生可见卡顿
- 新增 DataModel::BuildDataFile / DataSet::AddPreparedFile:不依赖 DataSet,
  因此可以安全地在线程上运行

内存与解析效率:
- CsvTable 由"行优先二维数组"改为"列优先":百万行文件原先会产生上百万次独立堆分配
  (每行还有 vector 控制块与容量对齐开销),现在每列一整块连续内存
- 解析改为流式分块读取(4MB),不再把整个文件读入内存,也没有体积上限
  (原先超过 512MB 直接拒绝);实测 51MB 文件峰值内存约 126MB(数据本身 113MB)
- BuildDataFile 直接把整列移动进 SignalData(零拷贝),不再逐元素复制
- TryParseDouble 增加十进制快速路径:ucrt strtod 实测约 335ns/次,手写路径约 13ns
  (快 25 倍);非十进制写法(十六进制浮点、inf/nan、带尾部杂字符)原样交回 strtod,
  输入接受语义完全不变(实测与 strtod 的差异不超过 1 ULP)
  → 640MB 测试文件加载时间由约 40s 降到 2s 以内

交互流畅性:
- 拖拽(平移/框选/拖动信号)期间关闭波形抗锯齿并压缩顶点预算(2 像素一列包络),
  松开后立即恢复全质量绘制;实测 BIG_DATA 10 信号全显示的帧耗时由
  105~168ms 降到 75~90ms(其中折线绘制 55ms→23ms)
- 折线包络抽稀改为"列边界单调推进"的二分,避免每列在全区间二分造成缓存缺失
- 未对波形构建引入多线程:构建已由几何键缓存消除,且拖拽降级后仅约 1~2ms/帧,
  并行化收益不足以抵消线程与同步的复杂度(加载路径的多线程收益明确,故只在该处引入)

图标:
- 窗口类与 WM_SETICON 原先使用系统默认图标 IDI_APPLICATION,导致标题栏、
  任务栏与 Alt+Tab 缩略图都显示成通用应用图标;改为按 SM_CXICON/SM_CXSMICON
  从应用的 IDI_APP_ICON 资源加载(多尺寸 ICO 会挑选最接近的一帧)

其他:
- PopupMenu 新增 EDIT 项类型(菜单内联输入框)与 UpdateItemValue/UpdateItemLabel
2026-09-30 18:46:56 +08:00
DeepSeek V4.1 Flash 12b3531485 输入框鼠标编辑、图例省略、信号跨视图共享选择与网格菜单布局修复
- 输入框:支持鼠标左键点击定位插入符、拖拽框选区域,并且不再限制字符长度
- 输入框:长文本使用 ETO_CLIPPED 裁剪绘制,避免字符溢出到编辑区域之外
- 图例:信号名称超过 200px 时省略号截断,不再挤压标尺区域
- 选中状态从 TabConfig 迁移到 DataSet,切换视图后信号选中状态保持一致
- 网格右键菜单:Y 轴最大值/最小值直接显示当前值,点击后原位置弹出内联编辑框
- 标尺数值改为绘制在图例下层,不再因避让图例而隐藏
- 移除已无用的 LegendBandRect,新增共享的编辑框布局与命中检测工具
- .gitignore 忽略 TestData 测试数据目录
2026-09-30 17:10:23 +08:00
DeepSeek V4.1 Flash e3744f3eff fix: 统一输入框编辑行为、外观跨视图同步并优化标尺与网格可读性
输入框(TextEdit,全组件共用):
- 修复退格一次删除两个字符:WM_CHAR 与 WM_KEYDOWN 重复处理了退格/删除,
  现统一由 OnEditKey 处理,WM_CHAR 忽略全部控制字符。
- 进入编辑(整段选中)后按右键:取消全选并把插入符移到文本最右侧。
- 存在选区时松开 Shift 再按左右方向键,插入符收拢到选区的左/右端。
- 修复整段选中后按 Home/End 不收拢选区的问题。

标签页:
- 按加粗字体测量宽度,避免激活页长名称被提前省略(预览过短)。
- 重命名过程中按正在输入的文本测量并加宽上限(320px),标签随输入增长,
  解决长内容绘制错误;输入溢出时整体等比收缩,保证「+」始终可见。

信号显示外观跨标签页同步:
- 外观(颜色/自定义色记忆/透明度/线宽/样式)从 TabConfig 上移到 DataSet
  的 SignalData,成为信号的全局属性;可见性仍按标签页独立保存。
- 因此任一视图中修改某信号的颜色/线宽/透明度/样式,其它视图自动一致。

工具栏:
- 移除「全选信号」按钮;「清空勾选」改为「清空网格」,
  「拆分信号网格」改为「拆分网格」。

自适应:
- 无可见信号(未勾选/全部取消/清空网格)时复位 ViewInitialized,
  下次勾选第一个信号会重新自适应,而不是停留在旧视图范围。

绘图区标尺与网格:
- 幅值刻度密度按网格高度自适应(3-8 档),各网格刻度数量一致;
  网格较矮时同步收紧标签行距,提高可读密度。
- 网格线与五等分估读线加深,与网格边界(蓝灰)保持明显区分。
- 时间轴列名补上单位,显示为 time (s)。
2026-09-30 15:16:53 +08:00
DeepSeek V4.1 Flash 261b8ac07d fix: 修正滑块填充残留、数值内联输入与信号/分组重命名,拖拽虚影跟随鼠标
- 滑块最小值时不再有超出圆钮的填充残留,三处滑块统一为共享实现
- 透明度/RGB/色号支持点击或双击直接输入,整段高亮、首次键入替换、回车提交、Ctrl+C 复制
- 双击信号行或分组名进入重命名编辑;分组竖线改为绘制而非字符
- 拖拽信号时曲线虚影跟随鼠标纵向位移
- 数据层新增展示名覆盖,面板改为持有可写数据集合
2026-09-30 09:38:22 +08:00
DeepSeek V4.1 Flash 17eb03a1b3 fix: 修复网格滚动溢出并重构绘图区缩放与时间光标交互
网格滚动:
- 修复纵向滚动时波形与文字溢出到工具栏/时间轴的问题:网格内容裁剪改为与绘图区求交,
  并对网格内的图例、幅值标签与读数框增加显式可见性判定
  (GDI 文本不遵循 GDI+ 裁剪区,仅裁剪矢量绘制不足以阻止溢出)。
- 网格之间改为完整矩形描边并加大间距,边界清晰;时间轴固定预留高度,不再被网格挤压。
- 时间轴刻度标签夹在绘图区横向范围内,避免与左侧时间列名重叠。

缩放交互:
- 取消滚轮缩放:滚轮改为纵向滚动网格,Shift+滚轮水平平移。
- 新增「时间轴缩放」「Y 轴缩放」工具栏切换按钮:选中后在绘图区拖拽选区放大对应轴向,
  拖动过程显示选区与跨度提示(时间用强调色、Y 轴用警示色);再次点击退出该模式。
- 「适应视图」回到初始状态:恢复自适应范围并退出缩放模式。
- 移除右键框选缩放,避免与模式化缩放重复。

时间光标:
- 改为工具栏「光标」按钮切换;启用时置于当前视图中心,再次点击关闭(Esc 亦可关闭)。
- 光标由鼠标靠近后拖拽移动,不再跟随鼠标;纵向贯穿全部网格并延伸到时间轴底部,
  顶部带三角把手提示可拖拽。
- 具体时间读数移至绘图区顶部,仅在鼠标覆盖光标时显示,不再遮挡时间轴刻度尺。
- 双击光标进入精确时间输入,交互与线宽输入一致:整段高亮、首次键入即整体覆盖、
  回车提交、Esc 取消、点击框外提交。
- 光标形态按模式区分:默认与悬停线外为箭头(不再出现十字指针),
  时间轴缩放/光标为水平双向箭头,Y 轴缩放为垂直双向箭头。
2026-09-29 19:32:37 +08:00
DeepSeek V4.1 Flash cba62c8218 fix: 修正样式区对齐与线宽输入交互,分组标题改为“组X|文件名”并支持同文件重复加载
样式编辑区:
- 标签列与内容列改为靠左对齐(原为整体居中),拉宽左侧面板时不再向中间偏移。
- 颜色行不再居中,与样式、透明度、线宽三行共用同一左边界与同一宽度上限
  (260 逻辑像素),实测四行均为 89-413px,右侧留 93px 空白。

线宽输入:
- 双击数值框后数值整体居中并高亮(外框与数值呈选中态),首次键入即整体覆盖原值,
  之后显示插入符;回车确认、Esc 取消、点击其它区域确认,交互与常见登录输入框一致。

散点绘制:
- 散点半径改为随线宽线性增长(0.5-10px 映射到 1.6-12px,系数 1.5),
  修复此前半径被上限 4px 过早截断、线宽较大时数据点不再放大的问题。

透明度描边:
- 新增统一色块绘制入口 UiDrawSwatch:按不透明度填充,且仅在不透明度低于 45% 时
  补一圈描边;可见时不绘制描边。
- 预设色块、自定义色块、信号行色块与波形图例色块统一走该入口,
  修复透明度过低时完全不可见的问题。

文件分组:
- 分组标题改为“组X|<文件名>”,序号使用强调色;序号按当前位置连续编号,
  关闭某个分组后其余分组自动重新编号。
- 移除按路径查重的限制,同一文件可多次加载并分别归入不同分组。
- 关闭分组改为“移至末位后弹出 + 收缩容量”,使该文件的时间轴与全部信号缓冲区
  在本次调用内立即析构归还系统。
- 确认加载后不再依赖原始文件:读取阶段即关闭句柄并整份载入内存,
  已实测删除源文件后仍可正常勾选与绘制波形。

验证:make clean && make all(Release+Debug)零错误零警告;
逐项以真实/合成鼠标与像素扫描核对以上行为。
2026-09-29 18:40:17 +08:00
DeepSeek V4.1 Flash dbe9b904ab fix: 重构样式区交互与布局,修复拾色浮窗穿透与自定义颜色记忆
样式编辑区统一为固定四行网格(颜色 / 样式 / 透明度 / 线宽):
- 四行共用同一内容列宽度,宽度带上下限(面板拉宽时不再无限延伸),
  标签列与内容列作为整体在面板内居中,行标签统一按整行垂直居中,
  修复“颜色”字样与色块存在高低差的问题。
- 颜色行的色块间距按剩余空间自适应,使该行与其它三行等宽对齐。
- 线宽改为与透明度同款的滑块(0.5–10 px),从原先的“预设 + 数值旋钮”
  调整为置于样式区最下方的滑块,保留双击数值直接键入的能力。

自定义颜色浮窗(ColorPickerPopup):
- 拖动滑块时不再与下方图层(尤其是绘图区平移/框选)产生交互:
  拾色浮窗内的按下会被完整消费并抓取鼠标,不再下传。
- 打开浮窗时光标正确显示为横向调整(IDC_SIZEWE),其余区域为箭头。
- 新增自定义颜色记忆:SignalStyle 记录 customColor,从自定义色切换到
  预设色后再次点击加号方格即可直接回到上次的自定义颜色;
  加号方格始终以该颜色填充(与预设色块同尺寸、同外框)。
- 打开浮窗时以记忆色为初始取值,不再重新从预设色起步。

透明度:
- 预设色块与自定义颜色方格同样叠加不透明度显示,
  修复此前透明度只影响自定义颜色预览的问题。

其他:
- 左侧面板宽度上限由 560 收紧至 400,避免样式控件被拉得过长。
- 移除只写不读的色块尺寸缓存与已废弃的线宽步进接口。

验证:真实鼠标输入逐项核对(拾色浮窗拖拽后绘图区视图范围与时间轴完全
不变、滑块光标 SIZEWE、自定义色 #0071C4 在切换预设后仍可一键恢复、
透明度 21% 时全部预设色块同步变淡、线宽滑块 8.1 px / 双击键入 5 设为
5.0 px、面板最窄与最宽两种极限下四行均不溢出且等宽)。
make clean && make all 零错误零警告。
2026-09-29 18:06:34 +08:00
DeepSeek V4.1 Flash 729494d1ec fix: 修复网格徽标菜单与标签页编号,重构样式区并新增透明度与浮动拾色器
信号列表
- 网格徽标(G1/G2)改为在左键抬起时弹出菜单,修复此前「按住才显示、松手即消失」的问题。
- 文件分组行新增关闭按钮(✕),可直接从工作区移除文件,与右键菜单「关闭该文件」等价。
- 信号名改为完整保留文件内的列名原文,不再剥离 "[n]" 序号前缀与括号内容。

标签页
- 新建标签页改用「当前未被占用的最小编号」,关闭标签页后编号可被复用,
  修复「开 3 关 2 再新建会变成视图 4」的问题。

样式编辑区(统一为固定四行网格布局,面板缩放时不再错位)
- 移除顶部「样式 · 信号名 · 文件名」标题行,选中信号即为编辑对象。
- 绘制方式取消「阶梯」,标签由「方式」改为「样式」,仅保留线段 / 散点 / 点线。
- 线宽仅保留最细与最粗两个预设,中间改为带左右箭头的数值旋钮,单位为 px;
  支持单击或双击数值框直接键入(首次键入替换原值,回车提交、Esc 取消、越界钳制到 0.5-6.0)。
- 颜色预设后新增与预设色块完全同尺寸、同外框样式的「加号」小方格,
  点击后弹出浮动拾色面板(与 G1/G2 菜单同风格的浮动窗口),支持 RGB 三通道滑块、
  实时预览与十六进制显示;点击面板外部即收起,取色实时应用到当前信号。
- 移除样式区末尾的网格选择控件,该功能统一由信号行的 G 徽标承载。
- 新增透明度滑块,默认 100%,可在 0-100% 之间调节,并实时反映到波形、
  图例色块与信号行色块的显示效果。

其他
- 信号面板默认宽度与行内间距微调,使较长的列名可以完整显示。
- 失焦时提交线宽输入并收起浮动拾色器与弹出菜单。
2026-09-29 17:14:27 +08:00
DeepSeek V4.1 Flash eb868b2679 feat: 重构信号样式区并统一主题化弹出菜单
样式编辑区:
- 移除顶部「样式 + 信号名 + 文件名」标题行,选中信号即代表编辑目标。
- 绘制方式取消阶梯选项,仅保留线段/散点/点线,标签由「方式」改为「样式」。
- 线宽改为「最细预设 + 数值旋钮 + 最粗预设」:中间为带左右增减箭头的输入框,
  步进 0.2px,取值范围 0.5-6.0px,并标注单位为 px。
- 颜色在 10 个预设色块后新增带加号图标的方格,点击展开 RGB 三通道滑块面板,
  可实时预览并显示十六进制值;点击面板外任意位置收起。
- 移除网格归属下拉框,改由每条信号行末尾的 G 徽标承载,点击弹出网格归属菜单。

信号面板与菜单:
- 文件分组行新增关闭按钮,可直接从工作区移除该文件,与整体风格保持一致。
- 新增 PopupMenu 组件:绘制在窗口后备缓冲区内的自绘菜单,含圆角、投影、悬停高亮、
  单选对勾、色块行与分组标题,彻底替换系统默认弹出菜单;
  菜单打开期间接管鼠标与键盘(Esc / 上下键 / 回车),失焦或在菜单外释放时自动关闭。
- 信号、文件、网格、标签页四类右键菜单全部改用该组件。

其他修复:
- 信号名称不再保留尾部单位括号,避免名称后再重复显示单位。
- 数据模型移除 DrawMode::STEP 枚举。
- 标签栏与信号面板弹出菜单后补充重绘请求,修复菜单不显示的问题。
2026-09-29 16:30:48 +08:00