新增实验性功能「自由标记」:网格内跟随鼠标的纵向光标 + 单击固定数据点 + 浮动面板
需求(用户):网格右键菜单「实验性功能」里新增带 √ 的「自由标记」。开启后网格内跟随鼠标
出现一条仅限本网格的纵向光标(上下各一个把手、不显示时间值),穿过信号时只标注离鼠标
最近的数据点;左键单击把该点固定(带 ✕ 可移除,鼠标移开仍显示、超出视界直接不显示),
后续固定点显示为相对前一个的增量;面板按信号分组列出「值(±增量) = 枚举」+「t:… Δt:…」
(两列右对齐、可双击按秒编辑并自动换位);命中自动点线判据的信号要有吸附提示;
两级 ✕ 都要悬停提示;与选区放大共存时原地单击 = 固定、按下拖动 = 框选。
一、数据层
- Lane 新增 freeMarker 开关、freeMarkerItems(FreeMarkerItem{ref, time})、freeMarkerPanel。
time 是「显示时刻」(画在 TimeToX(time) 处),与光标同一口径;取值必须走
SampleDisplayedSignalAt(内部处理时间偏移与水平镜像)。
- 新增 AddFreeMarker / RemoveFreeMarker / RemoveFreeMarkerAt / RemoveFreeMarkersOf /
FindFreeMarker / HasFreeMarkers。同一信号同一时刻不重复加入(相对容差 1e-12);
freeMarkerItems 保持插入顺序,显示顺序与「前一个固定点」一律由 SortedFreeMarkerIndices
按时间排序得出(删除某项不打乱其它项下标)。
- LaneCarry 同步携带,勾选/取消勾选信号导致网格重建时不会丢掉已固定的点。
二、网格内交互
- ProbeFreeMarker:仅当鼠标所在网格开启该功能时有效;在网格内全部可见信号里挑屏幕纵向
距离鼠标最近的数据点。MinorCellSparse 成立时经 SnapTimeToSample 吸附到真实采样时刻,
绘制侧画一圈圆环作为吸附提示。吸附只看判据,不看当前绘制方式。
- PaintFreeMarker(接在 paintOverlays 里、浮动面板之前):纵向线只在本网格内,上把手尖端
朝下(与常规光标顶部把手一致)、下把手尖端朝上,不画时间读数;已固定点画低透明度细线 +
圆点 + 「值(±增量) = 枚举」胶囊 + 行尾 ✕(悬停高亮 + 提示气泡);
超出视界直接不显示(不复用常规数据点的贴边定格 + 方向三角)。
- 网格内固定点 ✕ 的命中矩形由绘制时登记(m_freeMarkerHits),因为它的位置取决于取值文本宽度。
- OnMouseLeave 清跟随光标与三处悬停态(已固定的点保留)。
三、单击 vs 框选
- OnLButtonDown 在开启该功能的网格上只登记 m_freeMarkerPendingFix,并把拖拽模式设为
「选区放大 ? RUBBER_SELECT : PAN」;OnLButtonUp 里只有「整段操作没有拖动且选区轴向未定」
才 AddFreeMarker,否则照旧框选/平移。
四、浮动面板(EdgePanelKind::MARK / PLOT_CMD_LANE_FREE_MARKER = 12)
- 每个可见信号一行名称(右侧带清除该信号全部固定点的 ✕),名称行下方按时间升序列出该信号的
固定点;没有任何固定点时第一行给灰色提示。
- 行 = 左侧「值(±增量) = 枚举文本」(无尖头括号、首项无增量)+ 右侧两列各自右对齐的
「t:<时刻>s」与「Δt:+<复合单位>」(自动单位匹配)+ 行尾 ✕。
两列右边界存进 EdgePanelLayout(markerTimeRight / markerDeltaRight),保证所有条目纵向对齐。
- 双击行:左半编辑自身时刻(FREE_MARKER_TIME),右半编辑相对前一固定点的间隔
(FREE_MARKER_DELTA,最左边的点没有前一项所以不进入编辑)。提交后只改 time,
顺序由排序函数自动重排(越过后续点即交换条目位置)。
- PanelSlotKey 把每个固定点的信号 + 时刻、全部 marker 行矩形与两列边界一起 mix。
五、一并修掉的两个自伤
1. EdgePanelWidth 的 MARK 分支原本逐行取采样值算文本宽度 —— 它被 EdgePanelLayoutFor 调用,
而后者在 TopPanelAtPoint / SyncPanelOrder 里每次鼠标移动调用数次,等于「鼠标一动就做几十次
采样查询」。改为固定宽度 kFreeMarkerPanelWidth(340 逻辑像素)+ 绘制侧 UiEllipsize。
2. PaintFreeMarker 与跟随光标的胶囊绘制原本是两份重复代码,抽 PaintFreeMarkerCapsule 共用;
FreeMarkerLabelRects 增加 text 出参,避免绘制侧重复求解一次取值。
验证(全离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/markcheck 31/31:探测有效与就近选择、原地单击恰好新增 1 个固定点、
选区放大共存(单击固定不缩放 / 拖动缩放不固定)、面板行数与时间升序、首项无增量、
后续项增量与 Δt 正确、两列右对齐、行尾 ✕ 只删 1 项、信号名 ✕ 清空该信号、
编辑 Δt 后移动并重排、深放大时吸附且时刻精确等于采样时刻、视界外固定点不绘制。
- 回归:editcheck 20/20、round3check 31/31、round4check 34/34、round5check 5/5、
convcheck 18/18;check 136/137(唯一失败项是本轮之前就存在的不稳定项,上一轮已用
git stash 在基线复现同一数值);真实 MF4 仍 283/8/18/24 个信号。
- make release / make debug 零警告。
- TargetCheck.exe 833,536 字节(814.0 KiB)—— 比上一轮 +24 KB,首次超出旧的 800 KiB
红线约 14 KB。已做的瘦身见上;剩余为新功能的真实代码量,若要回到 800 KiB 以内需要
改变取舍(动态 CRT / UPX / 砍其它功能),本轮按「实现功能优先」未动。
文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 181-188 条硬约束
与文末本轮小节。
This commit is contained in:
1 parent
53b981a2a9
commit
dd8f63aa0e
10 files changed
+1735
-16
No files matched your search
@@ -765,6 +765,23 @@ namespace jdi
|
||||
int maxIterations = 10; /**< 「迭代次数 N」滑块的上限(用户可在面板上修改,恒 ≥ 1)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」中一个**固定**的数据点。
|
||||
*
|
||||
* 用户开启「自由标记」后,网格内会跟随鼠标出现一条纵向光标;左键单击一次就把当时
|
||||
* 就近的那个数据点固定下来。固定点按信号分组显示在浮动面板里,
|
||||
* 同一信号内的条目按**时间从左到右**排列(面板里从上到下)。
|
||||
*
|
||||
* ⚠️ `time` 存的是**显示时刻**(= 画在 `TimeToX(time)` 处),
|
||||
* 与 `TabConfig::CursorTime()` 同一口径;采样查询要用
|
||||
* `SampleDisplayedSignalAt`(它内部会减掉 `style.offsetTime`)。
|
||||
*/
|
||||
struct FreeMarkerItem
|
||||
{
|
||||
SignalRef ref; /**< 目标信号。 */
|
||||
double time = 0.0; /**< 显示时刻(秒)。 */
|
||||
};
|
||||
|
||||
std::wstring name; /**< 网格名称。 */
|
||||
std::vector<SignalRef> signals; /**< 该网格包含的信号(按加入顺序)。 */
|
||||
bool autoRange = true; /**< 是否依据信号取值范围自动计算 Y 轴。 */
|
||||
@@ -900,6 +917,58 @@ namespace jdi
|
||||
|
||||
/** 弱峰调整浮动面板的 UI 状态(展开 + 位置)。 */
|
||||
LanePanelState weakPeakPanel;
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 自由标记」开关。
|
||||
*
|
||||
* 开启后本网格内跟随鼠标显示一条**仅限本网格**的纵向光标(上下各一个三角把手,
|
||||
* 不显示时间读数);光标穿过信号时只标注**离鼠标最近**的那个数据点。
|
||||
* 左键单击把该数据点固定下来(固定点带 ✕ 可单独移除),浮动面板按信号分组列出
|
||||
* 「值(±增量) = 枚举文本」+「t:… Δt:…」。
|
||||
*/
|
||||
bool freeMarker = false;
|
||||
|
||||
/**
|
||||
* @brief 已固定的自由标记点(**未排序**,按加入顺序)。
|
||||
*
|
||||
* 面板内的显示顺序是按「网格内信号顺序 → 该信号内按时间升序」临时计算的,
|
||||
* 这样删除某一项不会打乱其它项的下标。同一信号同一时刻不会被重复加入。
|
||||
*/
|
||||
std::vector<FreeMarkerItem> freeMarkerItems;
|
||||
|
||||
/** 取某信号在某时刻的固定点下标(未找到返回 -1)。 */
|
||||
int FindFreeMarker(const SignalRef& ref, double time) const;
|
||||
|
||||
/**
|
||||
* @brief 加入一个固定点(同一信号同一时刻已存在时不重复加入)。
|
||||
* @return 新加入返回 true。
|
||||
*/
|
||||
bool AddFreeMarker(const SignalRef& ref, double time);
|
||||
|
||||
/** 移除某信号在某时刻的固定点(按时刻容差匹配)。 */
|
||||
void RemoveFreeMarker(const SignalRef& ref, double time);
|
||||
|
||||
/** 移除某个下标对应的固定点。 */
|
||||
void RemoveFreeMarkerAt(std::size_t index);
|
||||
|
||||
/** 移除某信号的**全部**固定点。 */
|
||||
void RemoveFreeMarkersOf(const SignalRef& ref);
|
||||
|
||||
/** 某信号是否已有固定点。 */
|
||||
bool HasFreeMarkers(const SignalRef& ref) const
|
||||
{
|
||||
for (const FreeMarkerItem& item : freeMarkerItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 自由标记浮动面板的 UI 状态(展开 + 位置)。 */
|
||||
LanePanelState freeMarkerPanel;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1401,6 +1401,89 @@ namespace jdi
|
||||
weakPeakItems.end());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 自由标记
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief 判断两个显示时刻是否算「同一个固定点」。
|
||||
*
|
||||
* 固定点是用户用鼠标点出来的,同一位置重复点击不应产生两条;但两次点击之间
|
||||
* 鼠标可能相差 1 位采样,因此用一个**极小**的相对容差判定而不是精确 `==`。
|
||||
*/
|
||||
bool SameMarkerTime(double a, double b)
|
||||
{
|
||||
if (a == b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const double scale = std::max(1.0, std::max(std::fabs(a), std::fabs(b)));
|
||||
return std::fabs(a - b) <= scale * 1.0e-12;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Lane::FindFreeMarker(const SignalRef& ref, double time) const
|
||||
{
|
||||
for (std::size_t index = 0; index < freeMarkerItems.size(); ++index)
|
||||
{
|
||||
const FreeMarkerItem& item = freeMarkerItems[index];
|
||||
if (item.ref == ref && SameMarkerTime(item.time, time))
|
||||
{
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Lane::AddFreeMarker(const SignalRef& ref, double time)
|
||||
{
|
||||
if (!ref.IsValid() || !std::isfinite(time))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (FindFreeMarker(ref, time) >= 0)
|
||||
{
|
||||
return false; // 同一信号同一位置只保留一个
|
||||
}
|
||||
FreeMarkerItem item;
|
||||
item.ref = ref;
|
||||
item.time = time;
|
||||
freeMarkerItems.push_back(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lane::RemoveFreeMarker(const SignalRef& ref, double time)
|
||||
{
|
||||
const int index = FindFreeMarker(ref, time);
|
||||
if (index >= 0)
|
||||
{
|
||||
freeMarkerItems.erase(freeMarkerItems.begin() + index);
|
||||
}
|
||||
}
|
||||
|
||||
void Lane::RemoveFreeMarkerAt(std::size_t index)
|
||||
{
|
||||
if (index >= freeMarkerItems.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 用移动 + `pop_back` 而不是 `erase`:固定点不带需要保持地址顺序的引用,
|
||||
// 但这样能让尾部缓冲立即析构,与其它移除路径保持一致的写法。
|
||||
for (std::size_t current = index; current + 1 < freeMarkerItems.size(); ++current)
|
||||
{
|
||||
freeMarkerItems[current] = freeMarkerItems[current + 1];
|
||||
}
|
||||
freeMarkerItems.pop_back();
|
||||
}
|
||||
|
||||
void Lane::RemoveFreeMarkersOf(const SignalRef& ref)
|
||||
{
|
||||
freeMarkerItems.erase(
|
||||
std::remove_if(freeMarkerItems.begin(), freeMarkerItems.end(),
|
||||
[&ref](const FreeMarkerItem& item) { return item.ref == ref; }),
|
||||
freeMarkerItems.end());
|
||||
}
|
||||
|
||||
int FindNearestSample(const TimeAxis& axis, double time)
|
||||
{
|
||||
const std::size_t count = axis.time.size();
|
||||
|
||||
@@ -88,6 +88,9 @@ namespace jdi
|
||||
bool weakPeak = false;
|
||||
std::vector<Lane::WeakPeakItem> weakPeakItems;
|
||||
LanePanelState weakPeakPanel;
|
||||
bool freeMarker = false;
|
||||
std::vector<Lane::FreeMarkerItem> freeMarkerItems;
|
||||
LanePanelState freeMarkerPanel;
|
||||
};
|
||||
|
||||
// 先记下每个信号原来所在网格的网格自身状态:
|
||||
@@ -125,6 +128,9 @@ namespace jdi
|
||||
carry.weakPeak = lane.weakPeak;
|
||||
carry.weakPeakItems = lane.weakPeakItems;
|
||||
carry.weakPeakPanel = lane.weakPeakPanel;
|
||||
carry.freeMarker = lane.freeMarker;
|
||||
carry.freeMarkerItems = lane.freeMarkerItems;
|
||||
carry.freeMarkerPanel = lane.freeMarkerPanel;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
previousCarry.emplace_back(ref, carry);
|
||||
@@ -167,6 +173,11 @@ namespace jdi
|
||||
lane.weakPeakPanel.open = carry.weakPeakPanel.open;
|
||||
lane.weakPeakPanel.x = carry.weakPeakPanel.x;
|
||||
lane.weakPeakPanel.y = carry.weakPeakPanel.y;
|
||||
lane.freeMarker = carry.freeMarker;
|
||||
lane.freeMarkerItems = carry.freeMarkerItems;
|
||||
lane.freeMarkerPanel.open = carry.freeMarkerPanel.open;
|
||||
lane.freeMarkerPanel.x = carry.freeMarkerPanel.x;
|
||||
lane.freeMarkerPanel.y = carry.freeMarkerPanel.y;
|
||||
};
|
||||
|
||||
// 收集全部可见信号引用。
|
||||
|
||||
Reference in new issue
Block a user