滚动过渡动画根治 + 快速连点跟手 + 分组名单击展开 + 光标吸附同源 + 按钮淡出对齐 + 滚动位置复位
一、滚动过渡动画卡顿(用户报告:甚至不如手动拖滚动条)—— 三处独立根因
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 条与变更日志一行。
This commit is contained in:
1 parent
34375481de
commit
436dcbfd52
12 files changed
+618
-182
No files matched your search
@@ -251,6 +251,24 @@ namespace jdi
|
||||
*/
|
||||
bool OnDoubleClick(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 双击落点是否应**当作普通「按下」**处理(该位置没有双击语义)。
|
||||
*
|
||||
* ⚠️ 窗口类带 `CS_DBLCLKS`:快速连点同一个控件时,第二次(及之后)按下会被 Windows
|
||||
* 合成为 `WM_LBUTTONDBLCLK`。若宿主把它交给 `OnDoubleClick`,而该位置本来只有
|
||||
* 「按下 / 单击」语义(浮动面板上的复选框与按钮、滚动条、GX 徽标…),那一次按下就
|
||||
* **完全丢失** —— 用户看到的正是“连点好几下、状态只切换了几次”(不跟手)。
|
||||
*
|
||||
* ✓ 本函数为这些位置返回 true,宿主改走 `OnLButtonDown`(与单击完全相同的路径)。
|
||||
* 只有真正有双击语义的位置(时间光标线 / 读数标签 / 图例条目名 / 绘图区空白处
|
||||
* 的自适应)返回 false。
|
||||
*
|
||||
* @param [IN] x 客户区横坐标。
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @return 应当作普通按下返回 true。
|
||||
*/
|
||||
bool DoubleClickIsPlainPress(int x, int y) const;
|
||||
|
||||
/**
|
||||
* @brief 处理鼠标离开绘图区。
|
||||
* @return 需要重绘返回 true。
|
||||
@@ -314,8 +332,8 @@ namespace jdi
|
||||
*
|
||||
* 用户 2026-10-17 **重新定义判据**(推翻此前的“一屏内数据点少于 N×4”):
|
||||
* 按**缩放后的横向时间轴网格**把小格切出来,**只要存在任何一个小格内的数据点数
|
||||
* 少于 `kAutoPointLineMinPointsPerCell`(=5)** 就自动切到「点线」提示用户数据点的位置
|
||||
*(见 `AutoPointLineNeeded`);一旦不再满足(适应视图、或把视界从稀疏区移到稠密区)
|
||||
* 少于 `kSparseMinPointsPerCell`(=5)** 就自动切到「点线」提示用户数据点的位置
|
||||
*(见 `MinorCellSparse`);一旦不再满足(适应视图、或把视界从稀疏区移到稠密区)
|
||||
* 自动恢复**原来的**绘制方式。
|
||||
*
|
||||
* ⚠️ 自动启用期间若用户**手动**改了绘制方式(`SignalStyle::autoScatterUserOverride`
|
||||
@@ -337,40 +355,29 @@ namespace jdi
|
||||
int GridMinorCellCount() const;
|
||||
|
||||
/**
|
||||
* @brief 判断某信号在当前视界下是否**开启光标吸附**。
|
||||
* @brief 判断某信号在当前视界下是否**按小格判定为稀疏**。
|
||||
*
|
||||
* 用户 2026-10-17 定义的判据(**推翻**此前的“一屏内数据点 < N×4”):
|
||||
* 按缩放后的**横向时间轴网格**把小格切出来(`GridMinorCellCount()`),
|
||||
* **只要存在任何一个小格内的有效数据点少于 `kSparseMinPointsPerCell`(=5)** 就返回 true。
|
||||
*
|
||||
* ⚠️ **这一个判定同时驱动两处**(用户 2026-10-17 明确要求“同样的状态判定”):
|
||||
* * 「放大自动切点线」(`UpdateAutoScatterStyles`);
|
||||
* * 「光标吸附」(`SnapTimeToSample`)—— 判定**完全不看绘制方式**,
|
||||
* 因此即使用户手动改过显示样式,该吸附的信号照样吸附。
|
||||
*
|
||||
* 用户 2026-10-15 的定义:**当且仅当**该信号在“当前一屏”(当前视界)内的数据点
|
||||
* 个数 `< N × 4`(N = `GridMinorCellCount()`)时开启吸附。
|
||||
* 判据随视界实时重算 ⇒ 疏密变化的信号在稀疏区自动吸附、稠密区自动自由移动。
|
||||
*
|
||||
* ⚠️ 额外要求视界内**至少有一个可见数据点**:数据点全在视界之外的信号既没有可吸附
|
||||
* 的对象、也没有可提示的内容(否则它会被判成“0 < N×4 稀疏”而白切一次「点线」)。
|
||||
* ⚠️ 额外要求视界内**至少有一个可见数据点**:数据点全在视界之外的信号既没有可吸附的
|
||||
* 对象、也没有可提示的内容(否则它会被判成“小格全空”而白切一次「点线」)。
|
||||
* ⚠️ 可见性一律按**显示时间**(`采样时刻 + timeShift`)判断 —— `timeShift` 就是该信号的
|
||||
* `SignalStyle::offsetTime`(存在时间轴偏移时不能拿源时刻去比视界)。
|
||||
*
|
||||
* @param [IN] axis 该信号使用的时间轴。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] timeShift 该信号的显示时间偏移(`offsetTime`)。
|
||||
* @return 开启吸附返回 true。
|
||||
* @return 存在少于 `kSparseMinPointsPerCell` 个数据点的小格返回 true。
|
||||
*/
|
||||
bool SignalSnapEnabled(const TimeAxis& axis, const SignalData& signal, double timeShift) const;
|
||||
|
||||
/**
|
||||
* @brief 判断某信号在当前视界下是否需要**自动切换到「点线」**(放大自动点线)。
|
||||
*
|
||||
* 用户 2026-10-17 **重新定义**的判据(推翻此前的“一屏内数据点 < N×4”):
|
||||
* 按缩放后的**横向时间轴网格**把小格切出来,**只要存在任何一个小格内的数据点数
|
||||
* 少于 `kAutoPointLineMinPointsPerCell`(=5)**,就返回 true。
|
||||
*
|
||||
* 其余规则不变:不再满足时恢复原绘制方式;用户手动改过绘制方式时不恢复
|
||||
*(见 `UpdateAutoScatterStyles`)。光标吸附**仍用** `SignalSnapEnabled` 的整屏判据。
|
||||
*
|
||||
* @param [IN] axis 该信号使用的时间轴。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] timeShift 该信号的显示时间偏移(`offsetTime`)。
|
||||
* @return 需要自动切换到「点线」返回 true。
|
||||
*/
|
||||
bool AutoPointLineNeeded(const TimeAxis& axis, const SignalData& signal, double timeShift) const;
|
||||
bool MinorCellSparse(const TimeAxis& axis, const SignalData& signal, double timeShift) const;
|
||||
|
||||
/**
|
||||
* @brief 判断某信号在给定可见下标区间内是否存在**落在视界内**的有效采样点。
|
||||
@@ -417,10 +424,13 @@ namespace jdi
|
||||
/**
|
||||
* @brief 把时间吸附到距今最近的数据采样点。
|
||||
*
|
||||
* 判定与半径(用户 2026-10-15 统一后的定义):
|
||||
* * **判定只针对鼠标所在的网格**:该网格内的信号各自按 `SignalSnapEnabled` 判定,
|
||||
* 符合条件的信号的数据点全部参与吸附;网格内没有符合条件的信号时完全自由移动;
|
||||
* * 同一网格内有多条信号时,所有符合条件的数据点都参与比较;
|
||||
* 判定与半径(用户 2026-10-17 修订):
|
||||
* * **判定只针对鼠标下方所在的网格**:该网格内的信号各自按 `MinorCellSparse`
|
||||
* (“任一小格内数据点少于 5 个”)判定,**符合条件的信号的数据点全部参与吸附**;
|
||||
* * 网格内**既有符合条件也有不符合条件的信号**时:只吸附符合条件那条信号的数据点
|
||||
* (用户明确要求);
|
||||
* * 网格内**没有**符合条件的信号时:光标完全自由移动(不吸附);
|
||||
* * 判定**不看绘制方式** ⇒ 即使用户手动改了显示样式,该吸附的信号照样吸附;
|
||||
* * 吸附半径 = **一个网格小格的宽度**(与判定同源),且**不超过半个局部采样间距**
|
||||
* ⇒ 数据点很稀疏(一屏只有一两个点)时光标在点与点之间仍可自由移动,
|
||||
* 而不是被“死死吸住”(吸附只是提示性响应,不是卡住光标)。
|
||||
@@ -3121,6 +3131,20 @@ namespace jdi
|
||||
* @return 可视返回 true。
|
||||
*/
|
||||
bool LaneVisuallyRelevant(int laneIndex) const;
|
||||
/**
|
||||
* @brief 在「可视」基础上再多算 `prefetchScreens` 个绘图区高度的**预取带**。
|
||||
*
|
||||
* ⚠️ 预取带只用于决定「是否还要请求下一帧去补图层」(`AnyVisibleLanePending` / 预热):
|
||||
* 网格滚动动画很可能在几百毫秒内扫过一整个屏高,若只预建「严格可见」的网格,
|
||||
* 动画扫进去的那几帧会落在一堆**尚未建过图层**的网格上 ⇒ 每帧都要靠底衬兜底
|
||||
* 画十几份网格(实测 ~8ms/帧)⇒ 动画明显卡顿(用户 2026-10-17 报告)。
|
||||
* 预取带让静置期间就把附近网格的图层建好。
|
||||
* ⚠️ **「结算」不能带预取带**(它要求的是“当前可见区域像素精确”)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] prefetchScreens 预取带宽度(倍绘图区高度,0 = 严格可见)。
|
||||
* @return 可视(或在预取带内)返回 true。
|
||||
*/
|
||||
bool LaneVisuallyRelevant(int laneIndex, int prefetchScreens) const;
|
||||
/** @brief 全部**可视**网格图层是否都已就绪(用于判断能否做结算的整幅精确帧)。 */
|
||||
bool LanesVisiblyReadyForView() const;
|
||||
/**
|
||||
@@ -3134,6 +3158,23 @@ namespace jdi
|
||||
* @return 仍有可视网格待重画返回 true。
|
||||
*/
|
||||
bool AnyVisibleLanePending() const;
|
||||
|
||||
/**
|
||||
* @brief 「图层预热」是否允许再请求一帧(**限速**)。
|
||||
*
|
||||
* ⚠️⚠️ 背景:待重画队列在“全部信号各自一块网格”的场景下可能有上百个**离屏**网格。
|
||||
* 若“队列非空就每帧都请求重绘”,会持续一秒以上地高频重绘,而 `WM_TIMER` 的优先级
|
||||
* **低于投递消息** ⇒ 动画定时器被饿死(实测 `frames=124 / 动画 tick=1`)。
|
||||
* 若反过来“离屏的一律不问”,则用户快速滚动到那些网格时它们**还没建过图层**,
|
||||
* 每帧要靠底衬兜底画十几份网格(实测 ~8ms/帧)⇒ 动画卡顿
|
||||
*(用户 2026-10-17 报告:MF4 + VERY_BIG_DATA 时点选最后一个信号,滚动明显卡)。
|
||||
*
|
||||
* ✓ 折中:以约 `kPrefetchMinIntervalMs` 的间隔**慢速**继续请求,让整批图层在静置期间
|
||||
* 逐步建好(对 130 个网格大约几秒),期间不会把动画的定时器挤出预算。
|
||||
*
|
||||
* @return 允许请求返回 true(并记录本次时刻)。
|
||||
*/
|
||||
bool PrefetchStepAllowed();
|
||||
/** @brief 把某网格排进"待重画"队列(去重;视界/跨度后续变化时会在渲染时再次校验)。 */
|
||||
void QueueLaneRebuild(int laneIndex);
|
||||
/**
|
||||
@@ -3250,6 +3291,14 @@ namespace jdi
|
||||
std::uint64_t m_wideLastKey = 0;
|
||||
bool m_wideLastValid = false;
|
||||
|
||||
/**
|
||||
* @brief 上一次为「图层预热」请求宿主重绘的时刻(`GetTickCount64()`)。
|
||||
*
|
||||
* 用来给预热**限速**(见 `PrefetchStepAllowed`):既保证离屏网格最终都会建好图层,
|
||||
* 又不会高频请求重绘把动画的 `WM_TIMER` 饿死。
|
||||
*/
|
||||
mutable std::uint64_t m_lastPrefetchRequestMs = 0;
|
||||
|
||||
/**
|
||||
* @brief 实验性功能浮动面板的像素图层项。
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user