修复浮动面板双击穿透、网格高亮触发来源、新增样式区「目标」行与联动平滑滚动
用户 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
@@ -1061,6 +1061,48 @@ namespace jdi
|
||||
{
|
||||
m_dataSetWritable->SetSelected(ref);
|
||||
}
|
||||
// 联动(用户 2026-10-15 要求):绘图区内把某条信号选为当前目标时,
|
||||
// 请求宿主把信号列表**平滑滚动**到它,保证在长列表里也能看到它。
|
||||
if (ref.IsValid())
|
||||
{
|
||||
m_pendingPanelScroll = ref;
|
||||
m_pendingPanelScrollFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PlotView::TakePanelScrollRequest(SignalRef& ref)
|
||||
{
|
||||
if (!m_pendingPanelScrollFlag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ref = m_pendingPanelScroll;
|
||||
m_pendingPanelScrollFlag = false;
|
||||
m_pendingPanelScroll = SignalRef();
|
||||
return ref.IsValid();
|
||||
}
|
||||
|
||||
void PlotView::SetExternalHoverSignal(const SignalRef& ref)
|
||||
{
|
||||
if (m_externalHoverSignal == ref)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_externalHoverSignal = ref;
|
||||
}
|
||||
|
||||
int PlotView::EffectiveHoverLane() const
|
||||
{
|
||||
if (m_hoverLane >= 0)
|
||||
{
|
||||
return m_hoverLane;
|
||||
}
|
||||
// 外部悬停信号(信号列表 / 样式区「目标」行):只在该信号确实绘制到某个网格时生效。
|
||||
if (m_tab != nullptr && m_externalHoverSignal.IsValid())
|
||||
{
|
||||
return m_tab->LaneOfSignal(m_externalHoverSignal);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void PlotView::ClampLaneScroll()
|
||||
@@ -6080,8 +6122,11 @@ namespace jdi
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 悬停网格:鼠标在面板上时高亮面板所属网格(用户明确要求)。
|
||||
const int lane = overEdgePanel ? panelLane : LaneAtY(y);
|
||||
// 悬停网格:**只有**鼠标悬停在实验性功能浮动面板上时才高亮该面板所属网格。
|
||||
// ⚠️ 用户 2026-10-15 明确要求:鼠标在网格里移动**不再触发**网格变色高亮;
|
||||
// 改为在信号列表 / 样式区「目标」行悬停到某条已绘制信号时联动高亮
|
||||
// (见 `SetExternalHoverSignal` 与 `EffectiveHoverLane`)。
|
||||
const int lane = overEdgePanel ? panelLane : -1;
|
||||
if (lane != m_hoverLane)
|
||||
{
|
||||
m_hoverLane = lane;
|
||||
@@ -6089,16 +6134,20 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 「GX」编号徽标的悬停高亮:徽标本体在静态场景 / 装饰缓存里被冻结,高亮是叠加层,
|
||||
// 因此必须靠这个**单独的状态**触发重绘与取消(鼠标在同一个网格的徽标范围内移动时
|
||||
// `m_hoverLane` 不变)。
|
||||
// 因此必须靠这个**单独的状态**触发重绘与取消。徽标命中的网格直接用鼠标纵坐标算,
|
||||
// 与上面的「悬停网格高亮」解耦(后者已不再跟随鼠标位置)。
|
||||
int hoverBadge = -1;
|
||||
if (m_dragMode == PlotDragMode::NONE && !overEdgePanel && lane >= 0)
|
||||
if (m_dragMode == PlotDragMode::NONE && !overEdgePanel)
|
||||
{
|
||||
const gd::RectF badge = LaneBadgeRect(lane);
|
||||
if (badge.Width > 0.0f &&
|
||||
UiPointInRect(badge, static_cast<float>(x), static_cast<float>(y)))
|
||||
const int underMouse = LaneAtY(y);
|
||||
if (underMouse >= 0)
|
||||
{
|
||||
hoverBadge = lane;
|
||||
const gd::RectF badge = LaneBadgeRect(underMouse);
|
||||
if (badge.Width > 0.0f &&
|
||||
UiPointInRect(badge, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
hoverBadge = underMouse;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoverBadge != m_hoverBadge)
|
||||
@@ -6826,6 +6875,19 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
// ⚠️ **上层元素的交互绝不能穿透到下层**:实验性功能的浮动面板浮在网格之上(且绘制在
|
||||
// 光标标签 / 图例之上),因此在它上面双击必须由它消费掉。旧实现在所有下层命中都不成
|
||||
// 立时无条件 `FitAll()`,于是"在面板上双击任意位置"会让网格莫名其妙地自适应一次
|
||||
// (用户 2026-10-15 报告)。面板自身双击没有额外语义,直接消费即可。
|
||||
{
|
||||
int panelLane = -1;
|
||||
int panelKind = 0;
|
||||
if (TopPanelAtPoint(static_cast<float>(x), static_cast<float>(y), panelLane, panelKind))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 双光标差值标签:双击进入差值编辑(输入值的量级始终为秒)。
|
||||
// 注意:差值标签在"两光标过近"时会落到主时间轴内部,因此必须先于区域判定。
|
||||
if (IsOnCursorSpanLabel(x, y))
|
||||
@@ -7835,9 +7897,12 @@ namespace jdi
|
||||
|
||||
// 悬停网格:一层很淡的强调色叠加。为了能画在静态场景之上,
|
||||
// 这里用半透明而非不透明混色,观感与原实现基本一致。
|
||||
if (m_hoverLane >= 0 && m_hoverLane < static_cast<int>(m_lanes.size()))
|
||||
// ⚠️ 高亮来源见 `EffectiveHoverLane()`(浮动面板悬停 / 信号列表悬停),
|
||||
// **不再**由"鼠标在网格里"触发。
|
||||
const int hoverLane = EffectiveHoverLane();
|
||||
if (hoverLane >= 0 && hoverLane < static_cast<int>(m_lanes.size()))
|
||||
{
|
||||
const gd::RectF& laneRect = m_lanes[static_cast<std::size_t>(m_hoverLane)].rect;
|
||||
const gd::RectF& laneRect = m_lanes[static_cast<std::size_t>(hoverLane)].rect;
|
||||
const float clipTop = std::max(laneRect.Y, m_plotRect.Y);
|
||||
const float clipBottom = std::min(laneRect.Y + laneRect.Height, m_plotRect.Y + m_plotRect.Height);
|
||||
if (clipBottom > clipTop)
|
||||
@@ -7849,16 +7914,23 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 悬停信号:外扩一圈半透明强调色作为"可拖拽"提示。
|
||||
if (m_hoverSignal.IsValid())
|
||||
// 鼠标在绘图区内直接命中时用 `m_hoverSignal`;信号列表 / 样式区悬停时用
|
||||
// `m_externalHoverSignal`(二者高亮方式相同,用户要求列表悬停也要高亮曲线本身)。
|
||||
SignalRef haloRef = m_hoverSignal;
|
||||
if (!haloRef.IsValid())
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(m_hoverSignal);
|
||||
haloRef = m_externalHoverSignal;
|
||||
}
|
||||
if (haloRef.IsValid())
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(haloRef);
|
||||
if (resolved.data != nullptr && resolved.style != nullptr && resolved.visible)
|
||||
{
|
||||
const int laneIndex = m_tab->LaneOfSignal(m_hoverSignal);
|
||||
const int laneIndex = m_tab->LaneOfSignal(haloRef);
|
||||
if (laneIndex >= 0 && laneIndex < static_cast<int>(m_lanes.size()))
|
||||
{
|
||||
const LaneVisual& lane = m_lanes[static_cast<std::size_t>(laneIndex)];
|
||||
const PolylineCacheEntry* cached = AcquirePolyline(m_hoverSignal, resolved, lane);
|
||||
const PolylineCacheEntry* cached = AcquirePolyline(haloRef, resolved, lane);
|
||||
if (cached != nullptr)
|
||||
{
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached);
|
||||
|
||||
Reference in new issue
Block a user