fix(ui): 采样统计显示一致化、GX 拖拽在布局边界下可用、信号「移动到…」与拖拽排序
一、采样间隔/频率显示不一致(用户报告 1ms vs 1000us、100us 0ns) * FormatDurationCompound 改为「先量化到整数皮秒、再整数分解」, 彻底消除同一数值因浮点噪声落到不同单位/留下零尾数的问题; 秒级分支与亚秒分支共用 totalPs 方案,次级进位/单位升级集中在一处。 * FormatFrequency 的单位选择改为按「舍入后的显示值」判定(999.9999999Hz 与 1000Hz 均为 1.000kHz),并做向下校正避免 0.001kHz。 二、选区截取面板 * 提示文本改为用户指定原文「启用双光标模式以进行选区截取」。 * EdgePanelWidth 计入该提示的实际文本宽度;绘制改用 UiTextClipped 不再省略。 * viewHeight 补回漏减的 bottomPad,修掉条目区与按钮之间的空白。 三、未展开的信号条目栏悬停不再变成手型光标。 四、开启「显示布局边界」后 GX 标签可正常拖拽排序 * 抽出 PaintLaneReorderOverlay(吸附槽位 + 被拖轮廓),贴图与整幅重绘共用。 * 整幅重绘在 LANE_REORDER 时按 LaneDragOffset 逐格平移重画(被拖网格最后画), 修掉调试模式下不跟随鼠标、无吸附提示、只能盲拖的问题。 五、信号右键新增「移动到…」二级菜单(位于「仅显示该信号」之上) * 条目为「分组 N(组名)」,当前组打勾;无标题行;底部「新建分组」。 * MenuItem::submenuMaxRows + PopupMenu 独立子菜单滚动(滚动条、滚轮、视口命中过滤), IsScrollable 计入子菜单可滚状态。 * 数据层 DataSet::MoveSignalToGroup/MoveSignal 让时间轴随信号搬运 (独占则移动、共享则复制);TabConfig::RemapAfterSignalMove 改写引用而非先删后插。 * Application::MoveSignalCore 统一入口,保证派生计算暂停、引用修正、选中迁移与同步一致。 六、信号列表支持按住拖拽排序(组内重排 / 跨组移动) * 4px 阈值进入拖拽;条目脱离原位跟随鼠标(阴影 + 强调色描边 + 插入指示线)。 * 其它信号行/分组行按插入位置做避让位移动画(RowRect 统一叠加插值位移, 宿主 kScrollTimerId 驱动 AdvanceSignalDragAnimation)。 * 数据层 DataSet::MoveSignal 支持任意位置;TabConfig::RemapAfterSignalReorder 线性重映射。 * 拖拽中禁用组名悬浮;双击改名与 InvalidateRows 均正确终止拖拽态。 验证:删 .o 全量重编译零 error / 零 warning;产物 873,984 B(< 1MB 预警阈值)。 本轮未使用鼠标/键盘/屏幕,未运行任何 GUI 探针,仅编译验证 + 源码级核对。
This commit is contained in:
1 parent
21a9865365
commit
97b361dedd
23 files changed
+1581
-74
No files matched your search
@@ -10015,3 +10015,100 @@ MEAN_SCROLL / 滚轮 / 悬停 / `SetLanePanelCollapsed` / `LanePanelCollapsed` /
|
||||
* 三处 `switch/tag` 均已逐个 grep 核对(`EdgePanelKind::RESAMPLE` 反查法)。
|
||||
* 删 `.o` 全量重编译:**零 error、零 warning**,产物 861,184 B(< 1MB)。
|
||||
* 用户要求不占用鼠标键盘屏幕 ⇒ 本轮**未跑任何 GUI 探针**,仅做编译验证与源码级核对。
|
||||
|
||||
---
|
||||
|
||||
## 2026-11-03 · 第 29 轮(提示文本 / 显示一致性 / GX 盲拖 / 移动到分组 / 拖拽排序)
|
||||
|
||||
### 1. ⭐⭐ 采样统计显示不一致(`1ms` vs `1000us`、`100us 0ns`)
|
||||
|
||||
**根因**:`FormatDurationCompound` 直接对 `double` 做 `floor` + `fraction * 1000`。
|
||||
同一个物理间隔在两个独立计算里可能是 `0.0010000000000000002` 与 `0.0009999999999999998`:
|
||||
* 前者落到 ms 单位 → `1ms`;后者落到 us 单位,`999.999…us` 进位成 `1000us`;
|
||||
* 次级单位的浮点噪声还会留下 `100us 0ns` 这种纯噪声尾数。
|
||||
|
||||
**现行规则(不得回退)**:
|
||||
* **先把时长量化成整数皮秒(`llround(value * 1e12)`),再用整数分解**,
|
||||
绝不对 `double` 做"接近 1000 就进位"的启发式判断。噪声在量化时被彻底消除,
|
||||
同一数值必然得到同一字符串。
|
||||
* 分解用 `decompose()` lambda:`whole = totalPs / unit.ps`,
|
||||
次级值以「千分之一个次级单位」为整数量纲(3 位小数一次舍入到位);
|
||||
次级舍入到 1000 ⇒ 进位到主单位;主单位到 1000 且无尾数 ⇒ 升级单位(`1000us` → `1ms`)。
|
||||
* 秒级分支(`value >= 1.0`)与亚秒分支用**同一套** `totalPs` 方案(前者用 `restPs / 1e6` 得毫秒千分位)。
|
||||
* `FormatFrequency`:单位选择必须按**舍入后的显示值**判断(`999.9999999 Hz` 与 `1000.0 Hz`
|
||||
必须都是 `1.000 kHz`),并做向下校正避免 `0.001 kHz`。
|
||||
|
||||
### 2. 选区截取面板:提示文本、宽度、底部空白
|
||||
|
||||
* 提示文本固定为 **`启用双光标模式以进行选区截取`**(用户指定原文,不得加长/改写)。
|
||||
* `EdgePanelWidth` 的 CLIP 分支必须把**这句提示的实际文本宽度**计入面板宽度,
|
||||
否则窄面板下提示会被省略成 `…`(用户要求"完整显示、不出现缺省")。
|
||||
提示本身用 `UiTextClipped`(不是 `UiEllipsize`):宁可贴边也不截断。
|
||||
* ⚠️ 面板高度算式是 `height = title + pad + view + bottomPad + footer`,
|
||||
因此 `viewHeight = height - title - pad - bottomPad - footer`。
|
||||
**漏减 `bottomPad` 就会在条目区与按钮之间留出一条空白**(用户 2026-11-03 报告的空白就是它)。
|
||||
|
||||
### 3. 条目栏(未展开)悬停不该改光标
|
||||
|
||||
`QueryCursor` 里信号条目栏的命中分支必须 `return nullptr`:
|
||||
它是**不可展开**的行,手型光标会误导用户以为可以点开(用户 2026-11-03 报告)。
|
||||
|
||||
### 4. ⭐⭐ 开启「显示布局边界」后 GX 标签只能盲拖
|
||||
|
||||
**根因**:网格重排的视觉全靠 `PaintLaneReorder`(把静态场景缓存按位移**贴图**平移),
|
||||
而它在 `m_forceFullRepaint`(调试叠加层)下被**刻意跳过**(贴回的旧像素会盖掉调试框)。
|
||||
跳过之后走整幅重绘,而整幅重绘**没有套用 `LaneDragOffset`** ⇒ 被拖网格不跟随鼠标、
|
||||
吸附槽位与轮廓都不出现 ⇒ 只能盲拖。
|
||||
|
||||
**修法**:
|
||||
* 把「吸附槽位高亮 + 被拖网格轮廓」抽成 `PaintLaneReorderOverlay(g)`,两条路径共用。
|
||||
* 整幅重绘的网格循环里,若正在 `LANE_REORDER`:
|
||||
用 `LaneVisual shifted = m_lanes[i]; shifted.rect.Y += LaneDragOffset(i);` 逐格平移重画,
|
||||
被拖网格最后画(压在最上层),装饰层之后再补 `PaintLaneReorderOverlay`。
|
||||
⚠️ **不要保留"先按原位置画一遍"的循环**,否则让位行会在原位留下残影。
|
||||
* `PaintLane` 只把 `const LaneVisual&` 的 `rect` 当几何来源(信号集合按 `laneIndex` 取),
|
||||
所以传平移副本即可让整条网格(底色/网格线/波形/装饰)一起移动。
|
||||
|
||||
### 5. ⭐⭐ 信号右键「移动到…」二级菜单(新增)
|
||||
|
||||
* 位置:**「仅显示该信号」之上**;条目为 `分组 N(组名)`,当前所在组打勾;
|
||||
**没有**「所属网格」标题行;末尾分隔线之下是「新建分组」。
|
||||
* `MenuItem::submenuMaxRows` 控制子菜单可滚动行数(本轮新增,默认 0 = 不限制)。
|
||||
`PopupMenu` 为此新增**独立的**子菜单滚动状态(`m_subScrollEnabled/m_subScrollOffset/
|
||||
m_subViewport/m_subScrollBarRect/m_subMaxRowsHeight`),不要与主菜单的 `m_scrollOffset` 混用。
|
||||
`BuildSubLayout` 开头会 `CloseSubmenu()` 清状态 ⇒ 必须在调用前保存 `m_subScrollOffset`
|
||||
并在**同一父项**重新排布时恢复,否则滚轮永远滚不动。
|
||||
`SubItemAtPoint` 必须先按 `m_subViewport` 过滤(否则滚出视野的条目还能点中)。
|
||||
`IsScrollable()` 要把「子菜单可滚」也算进去,否则宿主收到滚轮会直接关菜单。
|
||||
* 数据层:`DataSet::MoveSignalToGroup` / `MoveSignal`。**时间轴必须随信号一起走**:
|
||||
独占 → move 到目标组并修正源组其余信号的 `axisIndex`;共享 → 复制一条。
|
||||
* 引用修正:`TabConfig::RemapAfterSignalMove(from, to, visible)` —— 与「先删后插」不同,
|
||||
它把**恰好等于源引用**的条目**改写**为目标引用(信号留在原网格、曲线不中断)。
|
||||
⚠️ 顺序不可调换:**先匹配改写,再做 ±1 位移**。先位移会让源组 `from+1` 的引用
|
||||
位移成 `from` 而被误判成被移动的信号(曾经的 BUG)。
|
||||
* `m_visible`:目标组在 `to.signalIndex` 处插入一列(取源可见性),源组删除一列。
|
||||
若是「新建分组」,必须先对各标签页 `SyncToDataSet` 补出新列,否则插不进去 ⇒ 信号变未勾选、曲线消失。
|
||||
* 宿主侧统一走 `Application::MoveSignalCore`,两处入口(菜单 / 拖拽)共用,
|
||||
保证暂停派生计算、引用修正、选中项迁移、`SyncTabsToDataSet` 的流程一致。
|
||||
|
||||
### 6. ⭐⭐ 信号列表拖拽排序(新增)
|
||||
|
||||
* 状态全部在 `SignalPanel`:`m_signalDragPending(<4px 阈值)/ m_signalDragging /
|
||||
m_signalDragRow / m_signalDragDropGroup / m_signalDragDropIndex /
|
||||
m_signalDragShift(目标位移)/ m_signalDragShiftCur(插值位移)`。
|
||||
* **避让位移只写一处**:`RowRect()` 末尾叠加 `m_signalDragShiftCur[row]`。
|
||||
该函数是行内全部派生的唯一几何来源 ⇒ 名称框/复选框/GX 徽标/✕ 自动跟随,不必逐个改。
|
||||
* 拖拽中的条目**不画在原位置**(主循环 `continue`),改由 `PaintSignalDragGhost` 画在鼠标处:
|
||||
复用 `m_rowYOverrideActive + m_rowYOverrideY`(与「组名悬浮」同一机制)临时把行画到鼠标 y,
|
||||
再加阴影 + 强调色描边 + 落点插入指示线。
|
||||
* 拖拽期间**禁用组名悬浮**(`stickyRow = -1`),否则钉住的组名会让让位观感错乱。
|
||||
* 动画由宿主 `kScrollTimerId` 驱动:`Render` 的重绘判定与定时器判定都要带上
|
||||
`m_panel.SignalDragAnimationActive()`,并每帧调 `AdvanceSignalDragAnimation()`(0.32 系数插值)。
|
||||
* 提交:`PostMessage(hwnd, WM_APP + 8 /* kMessageReorderSignal */)`,
|
||||
`wParam = (源分组 << 16) | 源信号`,`lParam = (目标下标 << 16) | 目标分组`(下标 0xFFFF = 末尾)。
|
||||
* 数据层 `DataSet::MoveSignal`:同组 → 只改顺序(时间轴完全不动);
|
||||
跨组 → 按「移动到分组」的规则搬运时间轴。`TabConfig::RemapAfterSignalReorder` 做线性下标映射
|
||||
(`from < to` 时 `(from, to]` 各前移一位)。
|
||||
* 交互注意:`BeginSignalDrag` 只在**行主体**(else 分支)登记;`OnLButtonUp` 最前面先判拖拽;
|
||||
`OnDoubleClick` 一开始就 `CancelSignalDrag()`(否则改名后轻微移动会把信号拖走);
|
||||
`InvalidateRows()` 必须清空全部拖拽状态(行下标与逐行位移都会失效)。
|
||||
Reference in new issue
Block a user