修复浮动面板双击穿透、网格高亮触发来源、新增样式区「目标」行与联动平滑滚动
用户 2026-10-15 第六轮报告的四个问题与要求(详见 Documents/agent_mem.md §9.34):
1. 在网格内的实验性功能浮动面板上双击任意位置会穿透到下层网格,导致网格触发自适应。
根因:PlotView::OnDoubleClick 在“差值标签 / 光标标签 / 光标线 / 图例”都不命中时
无条件调用 FitAll()。
修法:在该函数最前面(高于一切下层命中)用 TopPanelAtPoint() 判定,命中浮动面板
则直接消费(面板自身双击无额外语义)。确立“上层元素的交互不得影响下层”的规则。
2. 未开启任何工具栏功能时鼠标移到网格上会让整个网格变色高亮(旧需求已不适用)。
修法:取消“鼠标在网格里移动就高亮”的行为;改为在信号选择区域悬停某条信号时,
若该信号已被勾选绘制到某个网格,则该网格变色高亮,并且该信号曲线本身也高亮;
鼠标移开即恢复。悬停未勾选的信号不产生任何高亮。
实现:PlotView 新增 m_externalHoverSignal + SetExternalHoverSignal()(与 m_hoverSignal
分开保存,因为绘图区 OnMouseLeave 会清掉后者,而列表悬停时鼠标不在绘图区内)、
EffectiveHoverLane();SignalPanel 新增 HoveredSignal(),并在 InvalidateRows()
里一并作废;Application::Render() 每帧同步(覆盖 WM_MOUSELEAVE / 数据变化 /
切换标签页等全部路径)。GX 徽标的悬停高亮仍按鼠标位置独立判定。
3. 在信号样式自定义区域最上方(预设颜色色块之上)新增「目标」行。
内容:当前正在被编辑的目标信号名称;若它已绘制到网格,则在名称后方显示所在 GX 标签。
交互:与信号选择区域的信号行一致 —— 名称/GX 标签悬停有高亮提示,且悬停时同样触发
该信号所在网格高亮 + 曲线高亮;单击不进入编辑(只触发联动滚动),双击才进入改名
(复用 StyleEditField::SIGNAL_NAME,m_editRow < 0 表示目标是选中信号)。
实现:SignalPanel 新增 m_targetRowRect / PaintTargetRow / UpdateTargetRowGeometry
(名称宽度与“是否有 GX 标签”都可能在不重排时变化,因此按需重算并带缓存键)、
StyleHit::TARGET_NAME / TARGET_BADGE;DesiredStyleHeight() 与 LayoutStyleControls()
相应多算一行。
4. 联动机制更新:从文件导入大量信号(如 MF4 的 124 条)时,被选中的信号可能被滚动到
可见范围之外而找不到。
修法:新增 SignalPanel::ScrollToSignalAnimated()(+ AdvanceScrollAnimation),
触发联动时把信号列表平滑滚动到目标信号完整可见:
* 目标在视界上方 ⇒ 向上滚动,且顶部让出一个组行高度,避免被悬浮组名遮挡;
* 目标在视界下方 ⇒ 向下滚动到完整露出;
* 已经完整可见则不动;
* ease-out cubic(越远越快、靠近变慢),时长 = clamp(距离×0.35ms, 130, 300),
总时长有上限;用户滚轮 / 拖滚动条立即取消动画。
触发点:绘图区内把某条信号选为当前目标(点击图例、点击信号曲线、双击图例改名)
⇒ PlotView::SelectSignal() 发布请求(TakePanelScrollRequest()),宿主在鼠标消息
处理后调用 ApplyPendingPanelScroll();样式自定义区的任何交互(预设色块、自定义
颜色入口、样式按钮、线宽/透明度滑块与数值框、「系数/偏移」「镜像」二级菜单、
绝对值)以及「目标」行的单击也都会触发同一次滚动。
宿主:Application 新增 16ms 定时器(kScrollTimerId)驱动动画,并由 Render 自管开关
(与提示浮层淡出同一套做法)。
验证(全部离屏,不占用鼠标 / 键盘 / 屏幕):
- .preflight/check(SW_HIDE + 只注入合成消息 + 进程/线程 BELOW_NORMAL)**73/73 通过**,
连跑 3 次稳定:[A] 浮动面板双击不移视界,且对照组(空白处双击)必须触发自适应;
[B] 鼠标在网格里移动不高亮;[C] 列表悬停 → 网格高亮 + 移开恢复 + 未勾选信号不高亮;
[D] 目标行的布局/占位/GX 标签位置与显隐/悬停联动/单击不编辑 + 双击改名(初始文本正确);
[E] 长列表 122 行:动画分多帧、单调、时长上限 300ms、目标完整可见且不被悬浮组名遮挡、
已可见时不动、滚轮取消动画;[F] 样式区交互与绘图区联动都会滚动到目标并完整显示;
[G] 布局回归(目标行在列表下方、样式区仍完整、默认无悬停与高亮)。
- A/B 判别:回退六处实现后 **15 项如实失败**(含"面板双击仍触发自适应"、"鼠标移到网格即高亮"、
"目标行不联动"、"联动瞬移而非动画")。
- make release:全部源文件重新编译 **零警告**;链接用临时 `make release TARGET_FILE_NAME=Target_check` 通过。
同步记忆:Documents/agent_mem.md 新增 §9.34、§11.2 第 72–75 条硬约束、变更日志新行。
This commit is contained in:
1 parent
73389f424c
commit
cb9d3e4b8f
7 files changed
+848
-32
No files matched your search
@@ -83,7 +83,15 @@ namespace jdi
|
||||
/**
|
||||
* @brief 标记列表结构失效(数据或折叠状态变化后调用)。
|
||||
*/
|
||||
void InvalidateRows() { m_rowsDirty = true; }
|
||||
void InvalidateRows()
|
||||
{
|
||||
m_rowsDirty = true;
|
||||
// 列表结构变化后行下标会失效 ⇒ 悬停联动高亮也必须一并作废,
|
||||
// 否则可能对一条已经不存在(或已被替换)的信号继续高亮它的网格。
|
||||
m_hoveredSignal = SignalRef();
|
||||
m_hoverRow = -1;
|
||||
m_hoverPart = RowPart::NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置 DPI 缩放比例。
|
||||
@@ -292,11 +300,48 @@ namespace jdi
|
||||
HCURSOR QueryCursor(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 将列表滚动到指定信号所在位置。
|
||||
* @brief 将列表滚动到指定信号所在位置(无动画,立即到位)。
|
||||
* @param [IN] ref 目标信号引用。
|
||||
*/
|
||||
void ScrollToSignal(const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 平滑滚动到指定信号(带动画)。
|
||||
*
|
||||
* 用户 2026-10-15 要求:触发联动机制(点击图例 / 信号曲线,或在样式自定义区与「目标」行
|
||||
* 做任何交互)时,要把信号列表**滚动**到目标信号完整可见:
|
||||
* * 目标在当前视界上方 ⇒ 向上滚动,直到它**完整露出且不被悬浮组名遮挡**;
|
||||
* * 目标在当前视界下方 ⇒ 向下滚动直到它完整露出;
|
||||
* * 滚动速度随距离变化(越远越快、靠近时变慢),但总时长有上限(不要拖得太久)。
|
||||
*
|
||||
* @param [IN] ref 目标信号引用。
|
||||
*/
|
||||
void ScrollToSignalAnimated(const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 推进一步滚动动画(由宿主定时器驱动)。
|
||||
* @return 动画仍在进行返回 true(宿主应保持定时重绘)。
|
||||
*/
|
||||
bool AdvanceScrollAnimation();
|
||||
|
||||
/** @brief 滚动动画是否正在进行。 */
|
||||
bool ScrollAnimationActive() const { return m_scrollAnimActive; }
|
||||
|
||||
/**
|
||||
* @brief 供绘图区联动高亮的「悬停信号」。
|
||||
*
|
||||
* 来源:① 列表里鼠标悬停的信号行;② 样式区「目标」行的信号名 / GX 标签。
|
||||
* 鼠标移开即返回无效引用。
|
||||
* @return 悬停的信号引用。
|
||||
*/
|
||||
const SignalRef& HoveredSignal() const { return m_hoveredSignal; }
|
||||
|
||||
/**
|
||||
* @brief 「目标」(正在编辑样式的)信号所在的网格下标。
|
||||
* @return 网格下标;未勾选绘制到任何网格时返回 -1。
|
||||
*/
|
||||
int TargetLaneIndex() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 样式区与列表行中可进行内联文本编辑的字段。
|
||||
@@ -306,7 +351,7 @@ namespace jdi
|
||||
NONE = 0, /**< 未处于编辑状态。 */
|
||||
WIDTH = 1, /**< 线宽数值。 */
|
||||
OPACITY = 2, /**< 不透明度数值(百分比)。 */
|
||||
SIGNAL_NAME = 3, /**< 信号显示名称(列表行内)。 */
|
||||
SIGNAL_NAME = 3, /**< 信号显示名称(列表行内;`row < 0` 表示样式区「目标」行)。 */
|
||||
FILE_NAME = 4 /**< 分组显示名称(列表行内)。 */
|
||||
};
|
||||
|
||||
@@ -349,6 +394,8 @@ namespace jdi
|
||||
GAIN_ADJUST, /**< 「系数」按钮(点击展开系数二级面板)。 */
|
||||
MIRROR_ADJUST, /**< 「镜像」按钮(点击展开镜像二级面板)。 */
|
||||
ABSOLUTE_TOGGLE, /**< 「绝对值」切换按钮。 */
|
||||
TARGET_NAME, /**< 样式区「目标」行的信号名。 */
|
||||
TARGET_BADGE, /**< 样式区「目标」行的 GX 网格标签。 */
|
||||
TOGGLE_ALL, /**< 全部显示/隐藏按钮。 */
|
||||
FILTER_CLEAR /**< 「筛选信号」输入框右侧的清空 ✕。 */
|
||||
};
|
||||
@@ -364,6 +411,35 @@ namespace jdi
|
||||
float height = 0.0f; /**< 行高(像素)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 计算「把某信号完整滚到可见」所需的滚动偏移。
|
||||
*
|
||||
* 顶部避让:当本组组名正在列表顶部悬浮时,列表顶部一段被组名占据,
|
||||
* 上行方向必须让目标行落在组名的下方才算“完整可见且不被遮挡”。
|
||||
*
|
||||
* @param [IN] ref 目标信号。
|
||||
* @param [OUT] desired 目标滚动偏移(未经 clamp)。
|
||||
* @return 找到了该行返回 true。
|
||||
*/
|
||||
bool SignalScrollTarget(const SignalRef& ref, float& desired) const;
|
||||
|
||||
/**
|
||||
* @brief 重新计算「目标」行内信号名与 GX 标签的矩形。
|
||||
*
|
||||
* 名称宽度依赖文本实际宽度、GX 标签是否出现依赖该信号是否已绘制到网格 —— 两者都可能在
|
||||
* 不重新布局的情况下变化(切换选中、勾选/取消勾选),因此用一个小缓存键避免重算。
|
||||
*/
|
||||
void UpdateTargetRowGeometry();
|
||||
|
||||
/**
|
||||
* @brief 绘制「目标」行(样式区最上方:名称 + GX 标签,可悬停 / 双击改名)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
*/
|
||||
void PaintTargetRow(gd::Graphics& g);
|
||||
|
||||
/** @brief 取消正在进行的滚动动画(用户主动滚动时调用)。 */
|
||||
void CancelScrollAnimation() { m_scrollAnimActive = false; }
|
||||
|
||||
/**
|
||||
* @brief 重新构建列表行集合。
|
||||
*/
|
||||
@@ -742,6 +818,32 @@ namespace jdi
|
||||
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
|
||||
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
|
||||
int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */
|
||||
/**
|
||||
* @brief 供绘图区联动高亮的悬停信号(列表信号行 / 样式区「目标」行)。
|
||||
*
|
||||
* 用户 2026-10-15 要求:鼠标悬停在信号列表里的某条信号(或样式区「目标」行的
|
||||
* 信号名 / GX 标签)上时,若该信号已勾选绘制到某个网格,则该网格变色高亮、
|
||||
* 且该信号曲线本身也高亮;鼠标移开即恢复。
|
||||
*
|
||||
* ⚠️ 不能用 `m_hoverRow` 现算:鼠标移出列表后它被清掉,而悬停可能仍在「目标」行上;
|
||||
* 且列表行会在数据变化时重建,行下标会失效。
|
||||
*/
|
||||
SignalRef m_hoveredSignal;
|
||||
/** 「目标」行(样式区最上方)的几何。 */
|
||||
gd::RectF m_targetRowRect;
|
||||
gd::RectF m_targetNameRect;
|
||||
gd::RectF m_targetBadgeRect;
|
||||
/** 「目标」行几何的缓存键(目标信号 + 所在网格 + 名称),避免每次鼠标移动都重算。 */
|
||||
SignalRef m_targetGeomRef;
|
||||
int m_targetGeomLane = -2; /**< -2 = 尚未计算过。 */
|
||||
std::wstring m_targetGeomTitle;
|
||||
bool m_targetGeomValid = false;
|
||||
/** 滚动动画状态(见 `ScrollToSignalAnimated` / `AdvanceScrollAnimation`)。 */
|
||||
bool m_scrollAnimActive = false;
|
||||
float m_scrollAnimFrom = 0.0f;
|
||||
float m_scrollAnimTo = 0.0f;
|
||||
std::uint64_t m_scrollAnimStartMs = 0;
|
||||
std::uint32_t m_scrollAnimDurationMs = 0;
|
||||
|
||||
StyleEditField m_editField = StyleEditField::NONE; /**< 当前处于输入状态的字段。 */
|
||||
int m_editRow = -1; /**< 行内输入所作用的列表行下标。 */
|
||||
|
||||
Reference in new issue
Block a user