「所属网格」菜单:标题固定 + 10 行上限 + 滚动条;网格滚动联动改为动态过渡
1) 菜单滚动时最上方的「所属网格」标题不再跟着滚走
- 根因:上一轮只实现了“尾部固定区”(最后一个分隔线 + 「新建网格」钉在底部),
而滚动区是从条目 0 开始的,于是标题也被一起滚掉。
- 修法:BuildLayout 新增 m_scrollFirst(= 前导 HEADER / HEADER_ACTION 的个数),
滚动区收敛为半开区间 [m_scrollFirst, m_pinnedIndex):
· 前导标题从菜单顶向下固定排(不参与滚动);
· 滚动区条目受 m_scrollOffset 影响;
· 尾部固定区从菜单底向上对齐。
- 配套:Paint 的裁剪与“跳过不可见条目”范围、ItemAtPoint 的“必须落在滚动视口内”判定、
EnsureItemVisible 的下界全部改用该区间;新增 ScrollRegion(first, last) 供查询/测试。
2) 菜单长度上限改为「最多 10 条网格」并加滚动条
- MenuLayoutConstraint 新增 maxScrollRows(滚动区最多显示的标准行数;与 maxHeight
同时给出时取更严格者);OpenLaneMenu 传 10(kLaneMenuVisibleRows),
另保留 maxHeight = max(180, 客户区高 × 0.8) 作为矮窗口的硬上限。
- 新增滚动条:轨道贴滚动条目区右内侧(不盖住标题与底部固定项),滑块高度按视口/内容比
(下限 22 逻辑像素);滚动区条目宽度让出 kScrollBarWidth + kScrollBarGap,
文本与对勾不会压在滑块上。
- 滚动条可拖拽:按下滑块 → 拖动(只改偏移、不更新悬停);点击轨道空白 → 滑块中心移过去;
松开结束拖拽且不下发命令、菜单保持打开。新增 ScrollOffsetForThumbTop 反算偏移。
- ⚠️ BuildLayout 绝不能复位 m_scrollBarDragging:拖拽时每帧都会 RebuildLayout(),
在那里清零会让拖拽“拖一下就脱手”(该状态只在 Open / Close 里复位)。
3) 网格滚动联动改为动态过渡(旧实现直接把视界瞬移过去,过于僵硬)
- PlotView::ScrollLaneIntoView 只负责启动平滑动画:与信号列表的“滚动到目标信号”同源
(ease-out cubic;时长 clamp(距离×0.35ms, 130, 300)),新增
m_laneScrollAnimActive/From/To/StartMs/DurationMs。
- 宿主 16ms 定时器(kScrollTimerId)调用 AdvanceLaneScrollAnimation() 逐帧推进,
与信号面板动画共用同一条定时器;Render 的“需要保持定时重绘”判定加入
LaneScrollAnimationActive()。
- 每帧 ClampLaneScroll() + BuildLaneVisuals()(网格滚动量参与场景/图层指纹与各网格 rect);
结束时精确落到目标。已完整可见的网格仍然不启动动画。
- ⚠️ 用户自己滚动网格(滚轮 / 拖滚动条)会 CancelLaneScrollAnimation(),避免与动画抢滚动位置。
验证(全部离屏执行:窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- .preflight/check 扩到 95 项,95/95 通过;新增断言:
· 菜单顶部标题在滚动时不动(Y 坐标不变)、滚动视口高度恰为 10 × 行高;
· 滚动条轨道/滑块存在、随滚动移动、按住滑块拖到底(菜单不关、不下发命令、末条目入视口)、
滚轮回到顶部;
· ScrollLaneIntoView 不瞬移(刚触发时几乎未动)、中间帧单调递进、收尾精确到位、
已可见时不启动动画、滚轮/手动滚动会取消动画。
- A/B:回退三处(标题固定 / 10 行上限 / 滚动动画)⇒ 10 项如实失败;恢复后 95/95。
- make release / make debug 零警告。
文档:Documents/agent_mem.md 新增 §9.39、§11.2 第 104–107 条与变更日志一行。
This commit is contained in:
1 parent
2904699eae
commit
82b5ebe4f9
10 files changed
+496
-78
No files matched your search
@@ -87,14 +87,21 @@ namespace jdi
|
||||
/**
|
||||
* @brief 菜单允许的最大高度(**客户区像素**,0 = 不限制)。
|
||||
*
|
||||
* 超过该高度时菜单不再变高:**最后一个分隔线及其之后的条目固定在菜单底部**
|
||||
* (典型用法:把「新建网格」钉在底部),其上方的内容成为可滚动的条目区
|
||||
*(鼠标滚轮 / 键盘方向键滚动)。
|
||||
* 超过该高度时菜单不再变高:**前导标题钉在顶部、最后一个分隔线及其之后的条目固定在底部**,
|
||||
* 中间的内容成为可滚动的条目区(鼠标滚轮 / 键盘方向键 / 滚动条)。
|
||||
*
|
||||
* 用于「条目数不定」的菜单:例如加载含上百个信号的 MF4 并把每个信号都放进独立网格时,
|
||||
* 「所属网格」菜单会有上百个条目(既超出屏幕、绘制开销也很大)。
|
||||
*/
|
||||
float maxHeight = 0.0f;
|
||||
|
||||
/**
|
||||
* @brief 可滚动条目区**最多显示的标准行数**(0 = 不限制)。
|
||||
*
|
||||
* 比 `maxHeight` 更直观(用户要求“保留 10 条网格的上限”):无论条目多高,
|
||||
* 滚动视口高度都不会超过该行数×标准行高。与 `maxHeight` 同时给出时**取更严格的那个**。
|
||||
*/
|
||||
int maxScrollRows = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -217,11 +224,47 @@ namespace jdi
|
||||
bool ContainsPoint(int x, int y) const;
|
||||
|
||||
/**
|
||||
* @brief 菜单是否因内容过长而启用了滚动(条目区可滚动、尾部分隔线之后固定到底部)。
|
||||
* @brief 菜单是否因内容过长而启用了滚动(条目区可滚动、前导标题与尾部条目固定)。
|
||||
* @return 启用滚动返回 true。
|
||||
*/
|
||||
bool IsScrollable() const { return m_open && m_scrollEnabled; }
|
||||
|
||||
/**
|
||||
* @brief 滚动条**轨道**矩形(仅可滚动菜单有效,其余返回空矩形)。
|
||||
*
|
||||
* 同时也是“可滚动条目区”的依据:条目区内的条目只在该矩形内可点。
|
||||
*/
|
||||
const gd::RectF& ScrollBarRect() const { return m_scrollBarRect; }
|
||||
|
||||
/**
|
||||
* @brief 滚动条**滑块**矩形(仅可滚动菜单有效)。
|
||||
* @return 滑块矩形;无需滚动时返回空矩形。
|
||||
*/
|
||||
gd::RectF ScrollBarThumbRect() const;
|
||||
|
||||
/**
|
||||
* @brief 由「滑块的顶部 Y」反推对应的滚动偏移(拖拽滚动条 / 点击轨道时用)。
|
||||
* @param [IN] thumbTop 期望的滑块顶部 Y(客户区坐标)。
|
||||
* @return 滚动偏移(已夹到合法范围)。
|
||||
*/
|
||||
float ScrollOffsetForThumbTop(float thumbTop) const;
|
||||
|
||||
/**
|
||||
* @brief 条目区当前的滚动偏移(仅可滚动菜单有效)。
|
||||
* @return 偏移量(像素,0 = 顶部)。
|
||||
*/
|
||||
float ScrollOffset() const { return m_scrollOffset; }
|
||||
|
||||
/**
|
||||
* @brief 可滚动条目区在菜单条目中的下标范围(半开区间 `[first, last)`)。
|
||||
*
|
||||
* 前导标题(`HEADER` / `HEADER_ACTION`)与末尾分隔线及其之后的条目**不参与滚动**。
|
||||
* @param [OUT] first 条目区首个条目下标。
|
||||
* @param [OUT] last 条目区末尾(不含)下标。
|
||||
* @return 菜单启用滚动返回 true;否则输出全区间。
|
||||
*/
|
||||
bool ScrollRegion(int& first, int& last) const;
|
||||
|
||||
/**
|
||||
* @brief 处理鼠标滚轮:滚动可滚动菜单的条目区。
|
||||
* @param [IN] delta 滚轮增量(`WM_MOUSEWHEEL` 的 `GET_WHEEL_DELTA_WPARAM`)。
|
||||
@@ -470,16 +513,20 @@ namespace jdi
|
||||
/** 最近一次使用的布局约束(`RebuildLayout` 复用)。 */
|
||||
MenuLayoutConstraint m_constraint;
|
||||
/**
|
||||
* @brief 尾部固定区(钉在菜单底部)的**首个条目下标**;-1 表示没有固定项。
|
||||
* @brief 可滚动条目区的下标范围(半开区间 `[m_scrollFirst, m_pinnedIndex)`)。
|
||||
*
|
||||
* 由 `BuildLayout` 在启用滚动时设为最后一个分隔线的下标:该分隔线及其之后的条目
|
||||
* (典型:「新建网格」)始终画在菜单底部,其上方的内容在滚动区内。
|
||||
* `m_scrollFirst` = 前导固定条目数(**「所属网格」这类标题必须始终留在最上方、不参与滚动**);
|
||||
* `m_pinnedIndex` = 末尾固定区起点(最后一个分隔线,典型「新建网格」钉在底部)。
|
||||
*/
|
||||
int m_scrollFirst = 0;
|
||||
int m_pinnedIndex = -1;
|
||||
bool m_scrollEnabled = false; /**< 条目区是否可滚动(内容超过 `maxHeight` 时启用)。 */
|
||||
bool m_scrollEnabled = false; /**< 条目区是否可滚动(内容超过约束上限时启用)。 */
|
||||
float m_scrollOffset = 0.0f; /**< 滚动区当前的滚动偏移(像素,0 = 顶部)。 */
|
||||
float m_scrollRegionHeight = 0.0f; /**< 滚动区的内容总高(像素)。 */
|
||||
gd::RectF m_scrollViewport; /**< 滚动区的可视矩形(也是滚动区条目的裁剪矩形)。 */
|
||||
gd::RectF m_scrollBarRect; /**< 滚动条轨道矩形(仅可滚动菜单有效)。 */
|
||||
bool m_scrollBarDragging = false; /**< 是否正在拖拽滚动条滑块。 */
|
||||
float m_scrollBarGrabOffset = 0.0f; /**< 拖拽时鼠标相对滑块顶部的偏移(像素)。 */
|
||||
|
||||
bool m_subOpen = false; /**< 二级子菜单是否展开。 */
|
||||
int m_subParentIndex = -1; /**< 展开子菜单的父项下标。 */
|
||||
|
||||
Reference in new issue
Block a user