目录规范化(Inc/Src)+「自由标记」第六轮交互调整
1) 目录规范化:所有 .hpp 只放 Inc,Src 只放 .cpp
- 4 个 *Internal.hpp 从 Src 迁到 Inc(DataModel/Application/PlotView/SignalPanel)
- Makefile 固化该约定(注释必须单独成行:写在同一行会把 CXX_INCLUDES_PATHS 整条注释掉)
- include 全是扁平形式,搬家无需改任何 include
2) 常规模式 = 普通鼠标(未开启「自由标记点选取」)
- ProbeFreeMarker 早退:不预览、不画纵向光标、单击不固定
- 补 if (!hitCurve) continue:否则整列光标下 probe.ref 恒有效,任意位置都能固定
- 固定点仍恒吸附到真实采样时刻;「已吸附」圆环只在稀疏判据成立时画
3) 两个显示开关:按钮改名「自由标记点选取」,其下新增「显示增量 | 显示枚举文本」
- 默认关闭,只影响已固定点的胶囊文本;预览胶囊恒为「值 = 枚举」
- 开关行计入面板高度与内容区高度
4) 面板六列:新增恒存在的「序号」列(组内 1 起)
- 枚举列存在时,无枚举/对不上任何枚举项显示 N/A
- 列与列之间画竖线;悬停某行时其它固定点封顶 50% 不透明度,移出恢复
- 删除固定点 ✕ 的悬停浮动提示(PaintFreeMarkerTooltip 实现与声明全部移除)
- 开启选取模式的网格上,信号悬停提示只显示信号名
5) 缓存键补齐
- FreeMarkerContents 混入枚举表指纹(旧键会让加上枚举表后仍显示 N/A)
- PanelSlotKey 混入两个新按钮矩形与开关状态
6) 注释/文档
- 修正 FreeMarkerProbe、SetLaneFreeMarkerCursor、标记透明度等处的过期说明
- 空态提示按是否开启选取模式区分文案
验证(全离屏,未占用鼠标/键盘/屏幕)
- 新增 .preflight/mark5check 52/52
- 回归:markcheck 67/67、mark4check 40/40、fixcheck 37/37、round3check 31/31、
round4check 34/34、round5check 5/5、editcheck 20/20、convcheck 18/18、diag 19/19
- check 136/137(唯一失败项为既有不稳定项,drift=15818 与改动前逐位相同)
- make release / make debug 零警告;Release 产物 TargetCheck.exe 857,600 字节
This commit is contained in:
1 parent
79eebc281e
commit
affb9cc340
12 files changed
+561
-187
No files matched your search
File renamed without changes.
@@ -945,32 +945,60 @@ namespace jdi
|
||||
bool ToggleLaneFreeMarker(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 设置某网格「自由标记」的**光标模式**(浮动面板最下方的切换按钮)。
|
||||
* @brief 设置某网格是否处于「自由标记点选取」(浮动面板底部的切换按钮)。
|
||||
*
|
||||
* 关闭(默认)时鼠标必须贴近信号曲线(稀疏判定下需靠近数据点)才显示取值预览,
|
||||
* 鼠标下方没有曲线时按下即普通的平移/框选;开启时与最初实现一致 ——
|
||||
* 网格内始终跟随鼠标画一条纵向光标。
|
||||
* 关闭(默认)= **普通鼠标**:不预览数据点、不画纵向光标、单击不固定任何点,
|
||||
* 按下/拖动/单击完全是常规语义(平移 / 框选 / 拖信号)。
|
||||
* 开启时网格内始终跟随鼠标画一条纵向光标,并在贴近曲线时预览/固定数据点。
|
||||
*
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否启用光标模式。
|
||||
* @param [IN] enabled 是否启用「自由标记点选取」。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneFreeMarkerCursor(int laneIndex, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 查询某网格是否处于「自由标记」的光标模式。
|
||||
* @brief 查询某网格是否开启了「自由标记点选取」。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 启用返回 true(该网格未开启自由标记时恒为 false)。
|
||||
*/
|
||||
bool LaneFreeMarkerCursor(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某网格「自由标记」的光标模式。
|
||||
* @brief 切换某网格的「自由标记点选取」。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 切换后的状态。
|
||||
*/
|
||||
bool ToggleLaneFreeMarkerCursor(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 设置某个网格「自由标记」固定点显示是否附带**括号内增量**。
|
||||
*
|
||||
* 只影响已固定点的取值胶囊(面板的「显示增量」开关),
|
||||
* 不影响跟随鼠标的预览胶囊与统计列。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否显示增量。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneFreeMarkerShowDelta(int laneIndex, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 设置某个网格「自由标记」固定点显示是否附带**枚举文本**。
|
||||
*
|
||||
* 只影响已固定点的取值胶囊(面板的「显示枚举文本」开关),
|
||||
* 不影响跟随鼠标的预览胶囊与统计列。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否显示枚举文本。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneFreeMarkerShowEnum(int laneIndex, bool enabled);
|
||||
|
||||
/** 查询某网格固定点是否显示增量。 */
|
||||
bool LaneFreeMarkerShowDelta(int laneIndex) const;
|
||||
|
||||
/** 查询某网格固定点是否显示枚举文本。 */
|
||||
bool LaneFreeMarkerShowEnum(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 设置某网格「自由标记」固定点的**显示不透明度**(0–1)。
|
||||
*
|
||||
@@ -1710,6 +1738,12 @@ namespace jdi
|
||||
struct MarkerRow
|
||||
{
|
||||
std::size_t itemIndex = 0;
|
||||
/**
|
||||
* @brief 该固定点在**本分组内**的序号(从 1 开始,组内按时间升序)。
|
||||
*
|
||||
* 面板最左侧「序号」列直接显示它;按分组重置(每组 = 一个信号)。
|
||||
*/
|
||||
std::size_t serial = 0;
|
||||
gd::RectF rect;
|
||||
gd::RectF close;
|
||||
};
|
||||
@@ -1758,8 +1792,17 @@ namespace jdi
|
||||
float markerContentHeight = 0.0f;
|
||||
gd::RectF scrollTrack; /**< 内容区右侧的滚动条轨道(不需要滚动时为空矩形)。 */
|
||||
gd::RectF scrollThumb; /**< 滚动条滑块。 */
|
||||
gd::RectF opacityTrack; /**< 「标记透明度」滑动条轨道(仅光标模式激活时非空)。 */
|
||||
gd::RectF opacityTrack; /**< 「标记透明度」滑动条轨道(总是显示,与选取模式无关)。 */
|
||||
gd::RectF opacityValue; /**< 「标记透明度」数值文本矩形。 */
|
||||
/**
|
||||
* @brief 「显示增量」开关按钮(位于「自由标记点选取」下方那一行的左半)。
|
||||
*
|
||||
* 它只控制**已固定点**的取值胶囊是否附带括号内增量,
|
||||
* 不影响跟随鼠标的预览胶囊,也不影响下面的统计列。
|
||||
*/
|
||||
gd::RectF showDelta;
|
||||
/** @brief 「显示枚举文本」开关按钮(同一行的右半)。 */
|
||||
gd::RectF showEnum;
|
||||
/**
|
||||
* @brief 「自由标记」:每个信号名右侧的「清除该信号全部固定点」✕。
|
||||
*
|
||||
@@ -1773,29 +1816,31 @@ namespace jdi
|
||||
*/
|
||||
std::vector<gd::RectF> rowNameRects;
|
||||
/**
|
||||
* @brief 「自由标记」面板的五列文本区
|
||||
* (① 取值 ② 枚举文本 ③ 增量 ④ 时刻 ⑤ 间隔)。
|
||||
* @brief 「自由标记」面板的**六列**文本区
|
||||
* (① 序号 ② 标记值 ③ 枚举文本 ④ 增量 ⑤ 时间点 ⑥ 时间间隔)。
|
||||
*
|
||||
* 每行都在这五列的**同一横坐标**上左对齐输出,因此同一信息在纵向上严格对齐。
|
||||
* 列宽固定(不按文本测量),绘制时用 `UiEllipsize` 截断,避免布局期查询采样。
|
||||
* 当整个面板里没有任何枚举文本时,② 枚举列的宽度为 0(整列缺省)。
|
||||
* 每行都在这六列的**同一横坐标**上左对齐输出,因此同一信息在纵向上严格对齐;
|
||||
* 相邻列之间画一条竖线作为分隔。
|
||||
* 列宽取「缺省下限」「实测内容宽度」「列标题宽度」三者的较大者;
|
||||
* 当整个面板里没有任何枚举文本时,③ 枚举列的宽度为 0(整列缺省)。
|
||||
*/
|
||||
struct MarkerColumn
|
||||
{
|
||||
float left = 0.0f;
|
||||
float width = 0.0f;
|
||||
};
|
||||
std::array<MarkerColumn, 5> markerColumns{};
|
||||
/** 面板里是否存在枚举文本(为 false 时 ② 列整列缺省)。 */
|
||||
std::array<MarkerColumn, 6> markerColumns{};
|
||||
/** 面板里是否存在枚举文本(为 false 时 ③ 列整列缺省)。 */
|
||||
bool markerHasEnumColumn = false;
|
||||
/** 五列的语义下标(`markerColumns` 的索引)。 */
|
||||
/** 六列的语义下标(`markerColumns` 的索引)。 */
|
||||
enum MarkerColumnIndex
|
||||
{
|
||||
MARKER_COL_VALUE = 0,
|
||||
MARKER_COL_ENUM = 1,
|
||||
MARKER_COL_DELTA = 2,
|
||||
MARKER_COL_TIME = 3,
|
||||
MARKER_COL_DT = 4
|
||||
MARKER_COL_SERIAL = 0,
|
||||
MARKER_COL_VALUE = 1,
|
||||
MARKER_COL_ENUM = 2,
|
||||
MARKER_COL_DELTA = 3,
|
||||
MARKER_COL_TIME = 4,
|
||||
MARKER_COL_DT = 5
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1813,19 +1858,19 @@ namespace jdi
|
||||
* 它描述「鼠标此刻所在的、开启了自由标记的网格」里应当标注的那个数据点:
|
||||
* 该网格内所有可见信号中**离鼠标最近**的那一个。
|
||||
*
|
||||
* 两种模式由 `Lane::freeMarkerCursor` 决定:
|
||||
* * **默认(非光标模式)**:只有鼠标**贴近某条信号曲线**时才有结果(`valid == true`),
|
||||
* 也**不画**纵向光标。⚠️ 判据 = 鼠标到**画出来的折线**的垂直距离 ≤
|
||||
* `kSignalHitTolerance`(与「常规状态下触发信号曲线高亮」**完全同一范围**;
|
||||
* 因此阶跃的竖直跳变段也能探测到)。判定通过后,标注时刻会被吸附到最近的**采样时刻**
|
||||
* (固定点必须落在真实数据点上);
|
||||
* * **光标模式**:网格内始终 `valid == true`(画纵向光标),能取到数据点时再标注取值
|
||||
* (此时只有命中「自动点线」稀疏判据的信号才吸附)。
|
||||
* 两种情形由 `Lane::freeMarkerCursor` 决定:
|
||||
* * **未开启「自由标记点选取」**:**没有结果**(`valid == false`)—— 鼠标就是普通鼠标,
|
||||
* 不预览数据点、不画纵向光标、单击也不固定任何点(用户 2026-10-23 要求);
|
||||
* * **已开启「自由标记点选取」**:网格内始终 `valid == true`(画纵向光标),但只有鼠标
|
||||
* **贴近某条信号曲线**时才有可固定的数据点(`ref` 有效)。⚠️ 判据 = 鼠标到**画出来的折线**
|
||||
* 的垂直距离 ≤ `kSignalHitTolerance`(与「常规状态下触发信号曲线高亮」**完全同一范围**;
|
||||
* 因此阶跃的竖直跳变段也能探测到)。命中后标注时刻会被吸附到最近的**采样时刻**
|
||||
* (固定点必须落在真实数据点上),但“已吸附”圆环只在命中「自动点线」稀疏判据时画。
|
||||
*/
|
||||
struct FreeMarkerProbe
|
||||
{
|
||||
bool valid = false; /**< 是否有结果(`ref` 有效,或光标模式下恒真)。 */
|
||||
bool cursorMode = false; /**< 该网格是否处于「光标模式」(决定是否画纵向光标)。 */
|
||||
bool valid = false; /**< 是否有结果(开启选取模式后恒真;`ref` 才是可固定的数据点)。 */
|
||||
bool cursorMode = false; /**< 该网格是否处于「自由标记点选取」(决定是否画纵向光标)。 */
|
||||
int laneIndex = -1; /**< 该网格下标。 */
|
||||
double time = 0.0; /**< 标注时刻(显示时刻;吸附时 = 采样时刻)。 */
|
||||
float x = 0.0f; /**< 该时刻的屏幕横坐标。 */
|
||||
@@ -1851,13 +1896,6 @@ namespace jdi
|
||||
*/
|
||||
void PaintFreeMarker(gd::Graphics& g, const std::vector<LaneVisual>& lanes);
|
||||
|
||||
/**
|
||||
* @brief 在鼠标附近画一个小提示气泡(与面板 GX 徽标的提示同一套样式)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] text 提示文本(空串则不绘制)。
|
||||
*/
|
||||
void PaintFreeMarkerTooltip(gd::Graphics& g, const std::wstring& text);
|
||||
|
||||
/** @brief 一个固定点 ✕ 的命中矩形(绘制时重建,命中测试时读取)。 */
|
||||
struct FreeMarkerHit
|
||||
{
|
||||
|
||||
+39
-22
@@ -340,7 +340,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 「自由标记」面板里每组分栏下方那一行**列表头**的高度(逻辑像素)。
|
||||
*
|
||||
* 表头 = 取值 / (枚举文本)/ 增量 / 时间点 / 时间间隔 五个列标题,
|
||||
* 表头 = 序号 / 标记值 / (枚举文本)/ 增量 / 时间点 / 时间间隔 的列标题,
|
||||
* 与数据行共用同一组列边界 ⇒ 表头与数据严格对齐。
|
||||
* ⚠️ 滚动时分组栏与表头会**各自吸顶**(见 `EdgePanelLayoutFor` 的 `MarkerGroup`),
|
||||
* 因此表头高度必须参与内容总高度与吸顶量计算。
|
||||
@@ -348,35 +348,34 @@ namespace jdi
|
||||
inline const float kFreeMarkerHeaderRowHeight = 18.0f;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板表头的五个列标题(顺序与 `markerColumns` 一致:
|
||||
* ① 取值 ② 枚举 ③ 增量 ④ 时刻 ⑤ 间隔)。
|
||||
* @brief 「自由标记」面板表头的列标题(顺序与 `markerColumns` 一致:
|
||||
* ① 序号 ② 标记值 ③ 枚举文本 ④ 增量 ⑤ 时间点 ⑥ 时间间隔)。
|
||||
*/
|
||||
inline const wchar_t* const kFreeMarkerColumnLabels[5] = {
|
||||
L"标记值", L"枚举文本", L"增量", L"时间点", L"时间间隔"};
|
||||
inline const wchar_t* const kFreeMarkerColumnLabels[6] = {
|
||||
L"序号", L"标记值", L"枚举文本", L"增量", L"时间点", L"时间间隔"};
|
||||
|
||||
/** 「自由标记」面板与行尾 ✕ 按钮的边长(网格内取值胶囊后的 ✕ 另按胶囊高度取正方形)。 */
|
||||
inline const float kFreeMarkerCloseSize = 13.0f;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板的宽度(逻辑像素)。
|
||||
* @brief 「自由标记」面板的宽度(逻辑像素,作为下限)。
|
||||
*
|
||||
* 取固定值而**不做逐行文本测量**:`EdgePanelWidth` 会被 `EdgePanelLayoutFor` 调用,
|
||||
* 而后者在 `TopPanelAtPoint` / `SyncPanelOrder` 里被高频调用(每次鼠标移动数次);
|
||||
* 逐行取采样值算宽度会变成"鼠标一动就做几十次采样查询"。
|
||||
*
|
||||
* 宽度 = 左右内边距 + 五列(取值/枚举/增量/时刻/间隔)+ 行 ✕ ⇒ 保证五列在纵向上对齐。
|
||||
* 实际宽度 = `max(本值, 六列实测/缺省/列标题宽度之和 + 行 ✕ + 滚动条预留)`;
|
||||
* 这里取固定下限而**不做逐行文本测量**(`EdgePanelWidth` 被 `EdgePanelLayoutFor` 调用,
|
||||
* 而后者在 `TopPanelAtPoint` / `SyncPanelOrder` 里被高频调用)。
|
||||
*/
|
||||
inline const float kFreeMarkerPanelWidth = 424.0f;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板五列的逻辑宽度
|
||||
* (**按此顺序**:取值 / 枚举文本 / 增量 / 时刻 / 间隔)。
|
||||
* @brief 「自由标记」面板六列的逻辑宽度下限
|
||||
* (**按此顺序**:序号 / 标记值 / 枚举文本 / 增量 / 时间点 / 时间间隔)。
|
||||
*
|
||||
* 固定列宽(不按文本测量,见上),绘制时用 `UiEllipsize` 截断超长文本。
|
||||
* 实际列宽取「本下限」「实测内容宽度」「列标题宽度」三者的**较大者**
|
||||
* ⇒ 面板里的数据与表头都不会出现省略号。
|
||||
* 当整个面板里没有任何枚举文本时,枚举这一列会被**整列缺省**(列宽记 0,
|
||||
* 后面的列左移),其它列的宽度不变。
|
||||
*/
|
||||
inline const float kFreeMarkerColumnWidths[5] = {74.0f, 80.0f, 60.0f, 72.0f, 78.0f};
|
||||
inline const float kFreeMarkerColumnWidths[6] = {34.0f, 74.0f, 80.0f, 60.0f, 72.0f, 78.0f};
|
||||
|
||||
/** 「自由标记」面板行内相邻两列之间的间距。 */
|
||||
inline const float kFreeMarkerColumnGap = 2.0f;
|
||||
@@ -395,6 +394,14 @@ namespace jdi
|
||||
/** 「自由标记」面板底部「标记透明度」滑动条行的高度(与信号样式区的行高同量级)。 */
|
||||
inline const float kFreeMarkerSliderRowHeight = 24.0f;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板底部相邻两行按钮之间的间隙(逻辑像素)。
|
||||
*
|
||||
* 底部固定区自上而下:`标记透明度` 滑块行 → `自由标记点选取` → `显示增量 | 显示枚举文本`。
|
||||
* 后两行之间用本间隙分开,避免两个按钮看起来连成一块。
|
||||
*/
|
||||
inline const float kFreeMarkerButtonGap = 4.0f;
|
||||
|
||||
/**
|
||||
* @brief 所有实验性功能浮动面板标题栏左侧「展开/折起」指示器的宽度。
|
||||
*
|
||||
@@ -429,24 +436,30 @@ namespace jdi
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板/胶囊里的取值部分:`值(+增量) = 枚举文本`。
|
||||
* @brief 「自由标记」面板/胶囊里的取值部分:`值[(±增量)] [= 枚举文本]`。
|
||||
*
|
||||
* 两个开关仅作用于**已固定点**的显示(「显示增量」/「显示枚举文本」):
|
||||
* 跟随鼠标的预览胶囊恒传 `showDelta=false, showEnum=true`(保持原有观感),
|
||||
* 面板统计列也不看这两个开关(它总是显示全部信息)。
|
||||
*
|
||||
* @param value 当前取值(已叠加「调整」变换)。
|
||||
* @param showDelta 是否显示括号内增量。
|
||||
* @param hasPrev 是否存在前一个同信号固定点(存在才显示增量)。
|
||||
* @param delta 相对前一个固定点的取值增量。
|
||||
* @param showEnum 是否显示枚举文本。
|
||||
* @param enumText 枚举文本(可为空指针)。
|
||||
*/
|
||||
inline std::wstring FreeMarkerValueText(double value, bool hasPrev, double delta,
|
||||
const std::wstring* enumText)
|
||||
inline std::wstring FreeMarkerValueText(double value, bool showDelta, bool hasPrev, double delta,
|
||||
bool showEnum, const std::wstring* enumText)
|
||||
{
|
||||
std::wstring text = FormatValue(value);
|
||||
if (hasPrev)
|
||||
if (showDelta && hasPrev)
|
||||
{
|
||||
text += L"(";
|
||||
text += FormatSignedValue(delta);
|
||||
text += L")";
|
||||
}
|
||||
if (enumText != nullptr && !enumText->empty())
|
||||
if (showEnum && enumText != nullptr && !enumText->empty())
|
||||
{
|
||||
text += L" = ";
|
||||
text += *enumText;
|
||||
@@ -565,8 +578,12 @@ namespace jdi
|
||||
inline const wchar_t* const kWeakPeakIterationsSliderLabel = L"迭代次数";
|
||||
inline const wchar_t* const kWeakPeakTitle = L"弱峰调整"; /**< 弱峰调整面板标题。 */
|
||||
inline const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱峰调整共用)。 */
|
||||
/** 「自由标记」面板底部切换按钮文案(切换按钮的激活态 = 光标模式开启)。 */
|
||||
inline const wchar_t* const kFreeMarkerCursorModeLabel = L"光标选取模式";
|
||||
/** 「自由标记」面板底部「自由标记点选取」切换按钮文案(激活态 = 光标模式开启)。 */
|
||||
inline const wchar_t* const kFreeMarkerCursorModeLabel = L"自由标记点选取";
|
||||
/** 「自由标记」面板底部「显示增量」切换按钮文案(只影响已固定点的显示)。 */
|
||||
inline const wchar_t* const kFreeMarkerShowDeltaLabel = L"显示增量";
|
||||
/** 「自由标记」面板底部「显示枚举文本」切换按钮文案(只影响已固定点的显示)。 */
|
||||
inline const wchar_t* const kFreeMarkerShowEnumLabel = L"显示枚举文本";
|
||||
/** 「自由标记」面板底部切换按钮激活时出现在其上方的滑动条标签。 */
|
||||
inline const wchar_t* const kFreeMarkerOpacityLabel = L"标记透明度";
|
||||
/**
|
||||
@@ -1693,6 +1693,54 @@ namespace jdi
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneFreeMarkerShowDelta(int laneIndex, bool enabled)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
if (lane.freeMarkerShowDelta == enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.freeMarkerShowDelta = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneFreeMarkerShowEnum(int laneIndex, bool enabled)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
if (lane.freeMarkerShowEnum == enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.freeMarkerShowEnum = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::LaneFreeMarkerShowDelta(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].freeMarkerShowDelta;
|
||||
}
|
||||
|
||||
bool PlotView::LaneFreeMarkerShowEnum(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].freeMarkerShowEnum;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneFreeMarkerOpacity(int laneIndex, float opacity)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
|
||||
@@ -966,12 +966,22 @@ namespace jdi
|
||||
// 滚动条与「标记透明度」滑块则是拖动类控件(上下 / 左右箭头)。
|
||||
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MARK)
|
||||
{
|
||||
// 底部「光标选取模式」切换按钮。
|
||||
// 底部「自由标记点选取」切换按钮与两个显示开关。
|
||||
if (layout.button.Width > 0.0f &&
|
||||
UiPointInRect(layout.button, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (layout.showDelta.Width > 0.0f &&
|
||||
UiPointInRect(layout.showDelta, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (layout.showEnum.Width > 0.0f &&
|
||||
UiPointInRect(layout.showEnum, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (layout.opacityTrack.Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.opacityTrack, m_scale, static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
@@ -2285,13 +2295,27 @@ namespace jdi
|
||||
if (kind == EdgePanelKind::MARK && m_tab != nullptr)
|
||||
{
|
||||
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
// 底部切换按钮「光标选取模式」(默认关闭)。
|
||||
// 底部切换按钮「自由标记点选取」(默认关闭)。
|
||||
if (layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||
{
|
||||
ToggleLaneFreeMarkerCursor(index);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 「显示增量」开关(只影响已固定点的显示;默认关闭)。
|
||||
if (layout.showDelta.Width > 0.0f && UiPointInRect(layout.showDelta, fx, fy))
|
||||
{
|
||||
SetLaneFreeMarkerShowDelta(index, !tabLane.freeMarkerShowDelta);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 「显示枚举文本」开关(只影响已固定点的显示;默认关闭)。
|
||||
if (layout.showEnum.Width > 0.0f && UiPointInRect(layout.showEnum, fx, fy))
|
||||
{
|
||||
SetLaneFreeMarkerShowEnum(index, !tabLane.freeMarkerShowEnum);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 「标记透明度」滑块:按下即写入该处数值并进入拖动。
|
||||
if (layout.opacityTrack.Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.opacityTrack, m_scale, fx, fy))
|
||||
@@ -2424,17 +2448,18 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// (2) 在开启「自由标记」的网格内按下:单击固定数据点、拖动仍是原有语义
|
||||
// (2) 在开启「自由标记点选取」的网格内按下:单击固定数据点、拖动仍是原有语义
|
||||
// (常规模式 = 平移;选区放大模式 = 框选)。
|
||||
// ⚠️ 用户明确要求:「仅单击时」才算固定数据点 —— 因此这里**只登记意图**,
|
||||
// 真正固定发生在 `OnLButtonUp` 且整段操作没有发生过拖动。
|
||||
// ⚠️ 默认(非光标模式)下 `ProbeFreeMarker` 只在鼠标贴近曲线时有效 ⇒
|
||||
// 鼠标下方没有信号时按下就是普通的平移/框选(用户明确要求)。
|
||||
// ⚠️⚠️ 只有「自由标记点选取」已开启才参与(用户 2026-10-23 要求):
|
||||
// 未开启时鼠标是普通鼠标,按下/拖动/单击完全是常规语义。
|
||||
if (!freeOverPanel)
|
||||
{
|
||||
const int freeLane = LaneAtY(y);
|
||||
if (freeLane >= 0 && freeLane < static_cast<int>(m_tab ? m_tab->Lanes().size() : 0) &&
|
||||
m_tab->Lanes()[static_cast<std::size_t>(freeLane)].freeMarker)
|
||||
m_tab->Lanes()[static_cast<std::size_t>(freeLane)].freeMarker &&
|
||||
m_tab->Lanes()[static_cast<std::size_t>(freeLane)].freeMarkerCursor)
|
||||
{
|
||||
const FreeMarkerProbe probe = ProbeFreeMarker(x, y);
|
||||
if (probe.valid && probe.ref.IsValid())
|
||||
|
||||
@@ -144,8 +144,8 @@ namespace jdi
|
||||
|
||||
if (kind == EdgePanelKind::MARK)
|
||||
{
|
||||
// 「自由标记」面板:宽度 = 五列(取「缺省列宽」与**实测内容宽度**的较大者)+ 行 ✕
|
||||
// + 左右内边距 + **滚动条预留位**(内容超高时右侧要放滚动条)。
|
||||
// 「自由标记」面板:宽度 = 六列(取「缺省列宽」「实测内容宽度」「列标题宽度」三者
|
||||
// 的大者)+ 行 ✕ + 左右内边距 + **滚动条预留位**(内容超高时右侧要放滚动条)。
|
||||
// ⚠️ 用户 2026-10-21 要求面板里的数据列**不得出现省略号**,因此不能再按固定列宽。
|
||||
// 逐行取采样值算宽度会拖慢交互(本函数被 `TopPanelAtPoint` / `SyncPanelOrder`
|
||||
// 高频调用),所以走 `Lane::freeMarkerColumnWidths`:绘制该面板时顺便量好真实
|
||||
@@ -163,7 +163,7 @@ namespace jdi
|
||||
return false;
|
||||
}();
|
||||
float columnsWidth = 0.0f;
|
||||
for (int column = 0; column < 5; ++column)
|
||||
for (int column = 0; column < 6; ++column)
|
||||
{
|
||||
const bool omitted = (column == EdgePanelLayout::MARKER_COL_ENUM) && !hasEnum;
|
||||
if (omitted)
|
||||
@@ -5460,8 +5460,7 @@ namespace jdi
|
||||
|
||||
// 「自由标记」面板:按信号分组(**分组栏** + 下属固定点条目),
|
||||
// 只列出确实有固定点的信号;没有任何固定点时给一行灰色提示。
|
||||
// 内容区超高时出现滚动条;面板底部固定「标记透明度」滑块(仅光标模式激活时)
|
||||
// 与「光标选取模式」切换按钮(二者**不参与**滚动)。
|
||||
// 底部固定区(自上而下):标记透明度滑块 → 「自由标记点选取」→ 两个显示开关。
|
||||
bool markerAny = false;
|
||||
for (const Lane::FreeMarkerItem& item : lane.freeMarkerItems)
|
||||
{
|
||||
@@ -5477,8 +5476,13 @@ namespace jdi
|
||||
const float headerRow = kFreeMarkerHeaderRowHeight * m_scale;
|
||||
const float itemRow = kFreeMarkerItemRowHeight * m_scale;
|
||||
const float markerButtonHeight = kEdgePanelButtonHeight * m_scale;
|
||||
// 「标记透明度」滑块行**始终显示**(用户 2026-10-21 要求:不再依附于光标模式按钮)。
|
||||
// 「标记透明度」滑块行**始终显示**(用户 2026-10-21 要求:不再依附于「自由标记点选取」按钮)。
|
||||
const float sliderRow = kFreeMarkerSliderRowHeight * m_scale;
|
||||
// 「显示增量 / 显示枚举文本」两个开关按钮独占一行,位于「自由标记点选取」下方
|
||||
//(用户 2026-10-23 要求)。
|
||||
const float switchRow = kEdgePanelButtonHeight * m_scale;
|
||||
const float buttonGap = kFreeMarkerButtonGap * m_scale;
|
||||
const float footer = sliderRow + markerButtonHeight + buttonGap + switchRow;
|
||||
float content = 0.0f;
|
||||
std::vector<std::size_t> order;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
@@ -5507,13 +5511,12 @@ namespace jdi
|
||||
content += groupRow;
|
||||
}
|
||||
// 内容区高度上限:太长的列表不能把面板顶出绘图区(超出部分靠滚动条翻页)。
|
||||
const float budget =
|
||||
m_plotRect.Height - (titleHeight + pad * 3.0f + markerButtonHeight + sliderRow);
|
||||
const float budget = m_plotRect.Height - (titleHeight + pad * 3.0f + footer);
|
||||
const float maxContent =
|
||||
std::max(2.0f * itemRow,
|
||||
std::min(kFreeMarkerContentMaxHeight * m_scale, std::max(2.0f * itemRow, budget)));
|
||||
const float view = std::min(content, maxContent);
|
||||
height = titleHeight + pad + view + pad + sliderRow + markerButtonHeight + pad;
|
||||
height = titleHeight + pad + view + pad + footer + pad;
|
||||
}
|
||||
|
||||
// 折起的面板:只剩标题栏(内容区完全不占高度)。
|
||||
@@ -5773,17 +5776,29 @@ namespace jdi
|
||||
const float itemRow = kFreeMarkerItemRowHeight * m_scale;
|
||||
const float closeSize = kFreeMarkerCloseSize * m_scale;
|
||||
const float markerButtonHeight = kEdgePanelButtonHeight * m_scale;
|
||||
// 「标记透明度」独立于光标模式按钮:**总是**显示在按钮上方。
|
||||
// 「标记透明度」独立于「自由标记点选取」:**总是**显示在按钮上方。
|
||||
const float sliderRow = kFreeMarkerSliderRowHeight * m_scale;
|
||||
const float scrollW = kFreeMarkerScrollWidth * m_scale;
|
||||
// 「显示增量 / 显示枚举文本」两个开关按钮:独占底部最后一行(左右各半个宽度)。
|
||||
const float switchRow = kEdgePanelButtonHeight * m_scale;
|
||||
const float buttonGap = kFreeMarkerButtonGap * m_scale;
|
||||
|
||||
// 底部固定区:切换按钮(整行宽度、与面板下边界留一个 pad)。
|
||||
layout.button = gd::RectF(left + pad, top + height - pad - markerButtonHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), markerButtonHeight);
|
||||
// 底部固定区(自上而下):标记透明度滑块 → 「自由标记点选取」→ 两个显示开关。
|
||||
{
|
||||
const float switchTop = top + height - pad - switchRow;
|
||||
const float halfWidth =
|
||||
std::max(0.0f, (width - pad * 2.0f - buttonGap) * 0.5f);
|
||||
layout.showDelta = gd::RectF(left + pad, switchTop, halfWidth, switchRow);
|
||||
layout.showEnum =
|
||||
gd::RectF(left + pad + halfWidth + buttonGap, switchTop, halfWidth, switchRow);
|
||||
// 「自由标记点选取」切换按钮(整行宽度)。
|
||||
layout.button = gd::RectF(left + pad, switchTop - buttonGap - markerButtonHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), markerButtonHeight);
|
||||
}
|
||||
{
|
||||
// 「标记透明度」行:与信号样式区的「透明度」行同一套排版
|
||||
//(左侧标签 + 中间轨道 + 右侧百分比),位置固定在按钮上方。
|
||||
// ⚠️ 无论是否启用「光标选取模式」都显示(用户 2026-10-21 要求)。
|
||||
// ⚠️ 无论是否启用「自由标记点选取」都显示(用户 2026-10-21 要求)。
|
||||
const float rowTop = layout.button.Y - sliderRow;
|
||||
const float valueWidth =
|
||||
std::max(36.0f * m_scale,
|
||||
@@ -5802,7 +5817,8 @@ namespace jdi
|
||||
|
||||
// 内容区高度(面板高度算式里的 `view` 部分)与"内容总高"决定是否需要滚动条。
|
||||
const float viewHeight = std::max(
|
||||
0.0f, height - titleHeight - pad * 3.0f - markerButtonHeight - sliderRow);
|
||||
0.0f, height - titleHeight - pad * 3.0f - markerButtonHeight - sliderRow -
|
||||
buttonGap - switchRow);
|
||||
float totalContent = 0.0f;
|
||||
std::vector<std::size_t> order;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
@@ -5848,9 +5864,9 @@ namespace jdi
|
||||
layout.scrollThumb = gd::RectF(layout.scrollTrack.X, thumbTop, scrollW, thumbHeight);
|
||||
}
|
||||
|
||||
// 五列(① 取值 ② 枚举 ③ 增量 ④ 时刻 ⑤ 间隔)从左往右**固定列宽**排布:
|
||||
// 每一行的同类信息都从同一横坐标开始 ⇒ 纵向上严格对齐(用户明确要求)。
|
||||
// ⚠️ 整个面板里没有任何枚举文本时,② 枚举列**整列缺省**(列宽记 0,后面的列左移)。
|
||||
// 六列(① 序号 ② 标记值 ③ 枚举文本 ④ 增量 ⑤ 时间点 ⑥ 时间间隔)
|
||||
// 从左往右排布:每一行的同类信息都从同一横坐标开始 ⇒ 纵向上严格对齐(用户要求)。
|
||||
// ⚠️ 整个面板里没有任何枚举文本时,③ 枚举列**整列缺省**(列宽记 0,后面的列左移)。
|
||||
// 这里只查"该信号是否带枚举表"(O(1)),**不**逐条采样(布局函数被高频调用)。
|
||||
bool hasEnum = false;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
@@ -5865,7 +5881,7 @@ namespace jdi
|
||||
}
|
||||
layout.markerHasEnumColumn = hasEnum;
|
||||
float columnX = left + pad;
|
||||
for (int column = 0; column < 5; ++column)
|
||||
for (int column = 0; column < 6; ++column)
|
||||
{
|
||||
const bool omitted = (column == EdgePanelLayout::MARKER_COL_ENUM) && !hasEnum;
|
||||
// ⚠️ 列宽取「缺省列宽」「实测内容宽度」与**表头列标题宽度**三者的大者:
|
||||
@@ -5957,10 +5973,13 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
rowY += headerRow;
|
||||
for (const std::size_t itemIndex : order)
|
||||
for (std::size_t orderPosition = 0; orderPosition < order.size(); ++orderPosition)
|
||||
{
|
||||
const std::size_t itemIndex = order[orderPosition];
|
||||
EdgePanelLayout::MarkerRow markerRow;
|
||||
markerRow.itemIndex = itemIndex;
|
||||
// 「序号」列:组内按时间升序从 1 开始(每组 = 一个信号)。
|
||||
markerRow.serial = orderPosition + 1;
|
||||
markerRow.rect = gd::RectF(layout.content.X, rowY, innerWidth, itemRow);
|
||||
markerRow.close = gd::RectF(layout.content.X + innerWidth - closeSize,
|
||||
rowY + (itemRow - closeSize) * 0.5f, closeSize,
|
||||
@@ -6445,6 +6464,8 @@ namespace jdi
|
||||
mixRect(layout.badge);
|
||||
mixRect(layout.edit);
|
||||
mixRect(layout.button);
|
||||
mixRect(layout.showDelta);
|
||||
mixRect(layout.showEnum);
|
||||
mixRect(layout.content);
|
||||
mixRect(layout.scrollTrack);
|
||||
mixRect(layout.scrollThumb);
|
||||
@@ -6508,6 +6529,10 @@ namespace jdi
|
||||
// 底部切换按钮的激活态、透明度滑块与内容滚动偏移直接改变面板像素 ⇒ 必须进键,
|
||||
// 否则开关 / 拖滑块 / 滚动之后仍会贴回旧像素。
|
||||
mix(lane.freeMarkerCursor ? 1ull : 0ull);
|
||||
// 「显示增量 / 显示枚举文本」两个开关直接改变已固定点的胶囊文本 ⇒ 必须进键
|
||||
// (否则切换开关后仍会贴回旧像素)。
|
||||
mix(lane.freeMarkerShowDelta ? 1ull : 0ull);
|
||||
mix(lane.freeMarkerShowEnum ? 1ull : 0ull);
|
||||
mixFloat(lane.freeMarkerOpacity);
|
||||
mixFloat(lane.freeMarkerScroll);
|
||||
// 选中行会改变整行底色 ⇒ 必须进键(否则单击行后贴回旧像素)。
|
||||
@@ -6740,6 +6765,9 @@ namespace jdi
|
||||
mix(bits);
|
||||
};
|
||||
mix(static_cast<std::uint64_t>(lane.freeMarkerItems.size()));
|
||||
// ⚠️ 两个显示开关只影响胶囊文本,但同样属于“与视界无关的输入”,必须进键。
|
||||
mix(lane.freeMarkerShowDelta ? 1ull : 0ull);
|
||||
mix(lane.freeMarkerShowEnum ? 1ull : 0ull);
|
||||
for (const Lane::FreeMarkerItem& item : lane.freeMarkerItems)
|
||||
{
|
||||
mix(static_cast<std::uint64_t>(static_cast<std::uint32_t>(item.ref.groupIndex)));
|
||||
@@ -6762,6 +6790,15 @@ namespace jdi
|
||||
mix(static_cast<std::uint64_t>(static_cast<int>(resolved.style->mode)));
|
||||
mix((resolved.style->absolute ? 1ull : 0ull) | (resolved.style->mirrorVertical ? 2ull : 0ull) |
|
||||
(resolved.style->mirrorHorizontal ? 4ull : 0ull));
|
||||
// ⚠️ 枚举表的内容决定「枚举文本」列与胶囊里的 `= <文本>`(以及缺省时的 `N/A`):
|
||||
// 旧实现只混了信号指针与采样数量 ⇒ 重新加载/替换枚举表后仍贴回旧文本
|
||||
//(离屏探针实测:加上枚举表后条目里仍是 `N/A`)。这里把表的规模与文本数组
|
||||
// 的地址一并入键 —— 只比较 `size()` 不足以区分“换成另一张同样长度的表”。
|
||||
mix(static_cast<std::uint64_t>(resolved.data->enumText.values.size()));
|
||||
mix(static_cast<std::uint64_t>(resolved.data->enumText.texts.size()));
|
||||
mix(static_cast<std::uint64_t>(resolved.data->enumText.rangeMin.size()));
|
||||
mix(static_cast<std::uint64_t>(
|
||||
reinterpret_cast<std::uintptr_t>(resolved.data->enumText.texts.data())));
|
||||
}
|
||||
|
||||
if (cache.key != key)
|
||||
@@ -6790,9 +6827,12 @@ namespace jdi
|
||||
entry.valid = valueOk;
|
||||
entry.value = value;
|
||||
entry.color = (resolved.style != nullptr) ? StyleColor(*resolved.style) : 0u;
|
||||
entry.capsuleText = FreeMarkerValueText(value, hasPrev, delta, enumPtr);
|
||||
entry.capsuleText = FreeMarkerValueText(value, lane.freeMarkerShowDelta, hasPrev, delta,
|
||||
lane.freeMarkerShowEnum, enumPtr);
|
||||
entry.valueText = valueOk ? FormatValue(value) : std::wstring(kFreeMarkerNotAvailable);
|
||||
entry.enumText = (enumPtr != nullptr) ? *enumPtr : std::wstring();
|
||||
// ⚠️ 枚举列一旦存在(面板里有任一固定点带枚举表),**没有**枚举文本的点必须显示
|
||||
// `N/A` 而不是空着(用户 2026-10-23 要求);数值对不上任何枚举项也落在这一分支。
|
||||
entry.enumText = (enumPtr != nullptr) ? *enumPtr : std::wstring(kFreeMarkerNotAvailable);
|
||||
entry.deltaText = (valueOk && hasPrev) ? FormatSignedValue(delta)
|
||||
: std::wstring(kFreeMarkerNotAvailable);
|
||||
entry.timeText = FreeMarkerTimeText(item.time);
|
||||
@@ -6988,6 +7028,10 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
const float markerAlpha = std::max(0.0f, std::min(1.0f, lane.freeMarkerOpacity));
|
||||
// 面板里正悬停着本网格的某一行 ⇒ 其余固定点“压暗”以突出目标
|
||||
//(用户 2026-10-23 要求:透明度高于 50% 时降到 50%,移出后恢复)。
|
||||
const bool rowHoverActive = (m_freeMarkerRowHoverLane == static_cast<int>(laneIndex) &&
|
||||
m_freeMarkerRowHoverItem != static_cast<std::size_t>(-1));
|
||||
// 内容(取值 / 文本 / 信号色)来自与视界无关的缓存,不逐帧重新采样。
|
||||
const std::vector<FreeMarkerContentEntry>& contents = FreeMarkerContents(static_cast<int>(laneIndex));
|
||||
for (std::size_t itemIndex = 0; itemIndex < lane.freeMarkerItems.size(); ++itemIndex)
|
||||
@@ -7015,13 +7059,16 @@ namespace jdi
|
||||
// 鼠标悬停在面板里这一行时:网格内对应的固定点整体高亮(用户 2026-10-22 要求)。
|
||||
const bool rowHot = (m_freeMarkerRowHoverLane == static_cast<int>(laneIndex) &&
|
||||
m_freeMarkerRowHoverItem == itemIndex);
|
||||
// 本行实际使用的不透明度:目标行强制不透明;其它行在“悬停联动”期间封顶 50%。
|
||||
const float rowAlpha =
|
||||
rowHot ? 1.0f
|
||||
: ((rowHoverActive && markerAlpha > 0.5f) ? 0.5f : markerAlpha);
|
||||
|
||||
// 数据点:与光标的数据点同一画法(圆点),同样受「标记透明度」控制。
|
||||
// 数据点:与光标的数据点同一画法(圆点),同样受「标记透明度」控制;
|
||||
// 悬停联动期间其它点封顶 50%(见 `rowAlpha`)。
|
||||
const float x = TimeToX(item.time);
|
||||
const float radius = 3.4f * m_scale;
|
||||
// 高亮(面板里悬停该行)时数据点必须完整可见 ⇒ 强制不透明。
|
||||
const float effectiveAlpha = rowHot ? 1.0f : markerAlpha;
|
||||
const int dotAlpha = static_cast<int>(std::lround(255.0f * effectiveAlpha));
|
||||
const int dotAlpha = static_cast<int>(std::lround(255.0f * rowAlpha));
|
||||
gd::SolidBrush dot(ToGdiColor(WithAlpha(signalColor, dotAlpha)));
|
||||
const float dotY =
|
||||
valueOk ? ValueToY(entry.value, visual) : (clipTop + clipBottom) * 0.5f;
|
||||
@@ -7043,10 +7090,10 @@ namespace jdi
|
||||
// ⚠️ 例外:如果此刻跟随探针正瞄着一个**被这个胶囊遮住的数据点**
|
||||
//(用户意图标记被遮住的那条曲线),则不恢复 —— 否则胶囊会重新遮住目标点、
|
||||
// 无法看出自己到底瞄到了哪个采样点。
|
||||
float capsuleAlpha = markerAlpha;
|
||||
float capsuleAlpha = rowAlpha;
|
||||
const float fx = static_cast<float>(m_mouseX);
|
||||
const float fy = static_cast<float>(m_mouseY);
|
||||
if (markerAlpha < 0.999f && UiPointInRect(label, fx, fy))
|
||||
if (capsuleAlpha < 0.999f && UiPointInRect(label, fx, fy))
|
||||
{
|
||||
const bool probeHiddenByLabel =
|
||||
m_freeMarker.valid && m_freeMarker.laneIndex == static_cast<int>(laneIndex) &&
|
||||
@@ -7071,7 +7118,7 @@ namespace jdi
|
||||
hit.laneIndex = static_cast<int>(laneIndex);
|
||||
hit.itemIndex = itemIndex;
|
||||
hit.close = close;
|
||||
if (markerAlpha > 0.02f)
|
||||
if (rowAlpha > 0.02f)
|
||||
{
|
||||
// 完全透明时不可见 ⇒ 也不该被点中(否则会“隔着看不见的按钮”移除固定点)。
|
||||
m_freeMarkerHits.push_back(hit);
|
||||
@@ -7079,7 +7126,7 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// ---- (2) 跟随鼠标的纵向光标(**仅「光标模式」**;默认不画,只给贴近曲线的取值预览)----
|
||||
// ---- (2) 跟随鼠标的纵向光标(**仅「自由标记点选取」开启时**;`probe.valid` 恒真)----
|
||||
const FreeMarkerProbe& probe = m_freeMarker;
|
||||
if (probe.valid && probe.laneIndex >= 0 &&
|
||||
probe.laneIndex < static_cast<int>(lanes.size()))
|
||||
@@ -7091,7 +7138,8 @@ namespace jdi
|
||||
float x = probe.x;
|
||||
x = std::max(visual.rect.X + kFreeMarkerLineInset * m_scale,
|
||||
std::min(x, visual.rect.X + visual.rect.Width - kFreeMarkerLineInset * m_scale));
|
||||
// ⚠️ 用户明确要求:默认**不启用**网格私有光标 —— 只有「光标模式」才画这条线。
|
||||
// ⚠️ 用户明确要求:未开启「自由标记点选取」时**连这条线都不画**(`ProbeFreeMarker` 直接早退,
|
||||
// `probe.valid` 为假);`cursorMode` 只是“确实处于选取模式”的双重保险。
|
||||
if (probe.cursorMode && clipBottom > clipTop)
|
||||
{
|
||||
UiDrawLine(g, x, clipTop, x, clipBottom, lineColor, 1.4f * m_scale);
|
||||
@@ -7118,7 +7166,10 @@ namespace jdi
|
||||
{
|
||||
const std::uint32_t signalColor = StyleColor(*resolved.style);
|
||||
const std::wstring* enumText = resolved.data->enumText.Find(probe.enumValue);
|
||||
const std::wstring text = FreeMarkerValueText(probe.value, false, 0.0, enumText);
|
||||
// 跟随鼠标的**预览**胶囊不受「显示增量 / 显示枚举文本」开关影响:
|
||||
// 它总是显示「值 = 枚举」(与两个开关的默认关闭状态无关)。
|
||||
const std::wstring text =
|
||||
FreeMarkerValueText(probe.value, false, false, 0.0, true, enumText);
|
||||
const float textWidth =
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, text) + 10.0f * m_scale;
|
||||
const float textHeight = 17.0f * m_scale;
|
||||
@@ -7152,38 +7203,8 @@ namespace jdi
|
||||
}
|
||||
|
||||
g.Restore(saveState);
|
||||
|
||||
// 悬停在固定点 ✕ 上时给出提示(与面板 ✕ 的提示风格一致)。
|
||||
if (m_freeMarkerHoverLane >= 0 && m_freeMarkerHoverItem != static_cast<std::size_t>(-1))
|
||||
{
|
||||
PaintFreeMarkerTooltip(g, L"点击移除此固定数据点");
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::PaintFreeMarkerTooltip(gd::Graphics& g, const std::wstring& text)
|
||||
{
|
||||
if (text.empty() || m_fonts == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float pad = 5.0f * m_scale;
|
||||
const float width = UiMeasureTextWidth(m_fonts->uiSmall, text) + pad * 2.0f;
|
||||
const float height = 18.0f * m_scale;
|
||||
float left = static_cast<float>(m_mouseX) + 12.0f * m_scale;
|
||||
float top = static_cast<float>(m_mouseY) + 16.0f * m_scale;
|
||||
if (left + width > m_plotRect.X + m_plotRect.Width - 2.0f * m_scale)
|
||||
{
|
||||
left = m_plotRect.X + m_plotRect.Width - width - 2.0f * m_scale;
|
||||
}
|
||||
if (top + height > m_plotRect.Y + m_plotRect.Height - 2.0f * m_scale)
|
||||
{
|
||||
top = static_cast<float>(m_mouseY) - height - 10.0f * m_scale;
|
||||
}
|
||||
const gd::RectF bubble(left, top, width, height);
|
||||
UiFillRoundRect(g, bubble, 4.0f * m_scale, m_theme->readoutBg);
|
||||
UiDrawRoundRect(g, bubble, 4.0f * m_scale, m_theme->borderStrong, 1.0f);
|
||||
UiText(g, text, m_fonts->uiSmall, bubble, m_theme->textStrong, TextAlign::CENTER);
|
||||
}
|
||||
|
||||
PlotView::FreeMarkerProbe PlotView::ProbeFreeMarker(int mouseX, int mouseY) const
|
||||
{
|
||||
@@ -7204,11 +7225,17 @@ namespace jdi
|
||||
{
|
||||
return probe;
|
||||
}
|
||||
// ⚠️⚠️ **未开启「自由标记点选取」时鼠标就是普通鼠标**(用户 2026-10-23 明确要求):
|
||||
// 不预览数据点、不画纵向光标、按下/拖动完全走常规语义(平移 / 框选 / 拖信号),
|
||||
// 也不会固定任何数据点。所有自由标记的探测都从这一句的“早退”开始。
|
||||
if (!lane.freeMarkerCursor)
|
||||
{
|
||||
return probe;
|
||||
}
|
||||
const LaneVisual& visual = m_lanes[static_cast<std::size_t>(laneIndex)];
|
||||
|
||||
// 光标模式(默认关闭):决定"是否在网格内始终显示纵向光标"以及
|
||||
// "鼠标离曲线多远还算有信号"。关闭时只有贴近曲线(或稀疏判定下贴近数据点)才有结果。
|
||||
const bool cursorMode = lane.freeMarkerCursor;
|
||||
// 「自由标记点选取」已开启:网格内始终跟随鼠标画纵向光标(见 `PaintFreeMarker`)。
|
||||
const bool cursorMode = true;
|
||||
probe.cursorMode = cursorMode;
|
||||
probe.laneIndex = laneIndex;
|
||||
probe.time = XToTime(static_cast<float>(mouseX));
|
||||
@@ -7362,16 +7389,21 @@ namespace jdi
|
||||
|
||||
// ---- ② 决定标注时刻:先取曲线上的最近点,再吸附到采样点 ----
|
||||
const bool hitCurve = (distance <= static_cast<double>(tolerance));
|
||||
if (!hitCurve && !cursorMode)
|
||||
// ⚠️ 只有**贴着曲线**的那根信号才是可标注目标:否则整列预览时会把“离鼠标很远
|
||||
// 的那根曲线的采样值”当成预览值(`probe.ref` 会一直有效 ⇒ 任意位置单击都能固定)。
|
||||
if (!hitCurve)
|
||||
{
|
||||
continue; // 非光标模式:鼠标不在曲线附近 ⇒ 该信号不参与
|
||||
continue;
|
||||
}
|
||||
double time = hitCurve ? XToTime(nearestX) : probe.time;
|
||||
double time = XToTime(nearestX);
|
||||
// ⚠️ **命中曲线时始终吸附到最近的采样点**:固定点必须落在真实采样时刻上
|
||||
//(第四轮的硬约束;否则面板里的「标记值」会变成插值出来的中间值)。
|
||||
// ⚠️ 但表示“已吸附”的那个圆环**只在「自动点线」判据成立(`sparse`)时**才画:
|
||||
// 密采样信号里吸附量远小于 1 像素,圆环会一直挂在那里,反而像是画错了。
|
||||
// ⚠️ 鼠标**没有**命中任何曲线时(上面已 `continue`)不存在吸附问题:那时的纵向
|
||||
// 光标由 `probe.time`(鼠标横坐标)驱动,光标不能自己跳到采样点上去。
|
||||
bool snapped = false;
|
||||
if (!cursorMode || sparse)
|
||||
{
|
||||
// 非光标模式恒吸附(固定点必须落在真实采样点上,阶跃两端才点得中);
|
||||
// 光标模式只在「自动点线」判据成立时吸附(保持原有手感)。
|
||||
double candidate = 0.0;
|
||||
if (snapToNearestSample(*resolved.axis, *resolved.data, *resolved.style, time, candidate))
|
||||
{
|
||||
@@ -7408,9 +7440,8 @@ namespace jdi
|
||||
|
||||
if (!probe.ref.IsValid())
|
||||
{
|
||||
// 网格内没有可标注的数据点:光标模式下仍要画纵向线(`valid` 为真),
|
||||
// 非光标模式下则**什么都不显示**,鼠标按下即普通的平移/框选。
|
||||
probe.valid = cursorMode;
|
||||
// 网格内没有可标注的数据点:「自由标记点选取」模式下仍要画纵向线(`valid` 为真)。
|
||||
probe.valid = true;
|
||||
return probe;
|
||||
}
|
||||
probe.valid = true;
|
||||
@@ -8027,7 +8058,7 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 「自由标记」面板:按信号分组的**分组栏**(信号色托底 + 三角 + 名称 +
|
||||
// 「N 标记点」+ ✕),下属固定点条目五列对齐(取值 / 枚举 / 增量 / 时刻 / 间隔),
|
||||
// 「N 标记点」+ ✕),下属固定点条目六列对齐(序号 / 取值 / 枚举 / 增量 / 时刻 / 间隔),
|
||||
// 行尾各带一个 ✕。内容区有滚动偏移时按内容区裁剪。
|
||||
if (freeMarker)
|
||||
{
|
||||
@@ -8043,7 +8074,12 @@ namespace jdi
|
||||
|
||||
if (!markerAny)
|
||||
{
|
||||
UiTextClipped(g, L"在网格内单击以固定数据点", m_fonts->uiSmall,
|
||||
// ⚠️ 未开启「自由标记点选取」时鼠标是普通鼠标,**单击不会固定**任何数据点
|
||||
//(用户 2026-10-23 要求)⇒ 提示语必须说清前提,否则用户会以为 bug。
|
||||
const wchar_t* emptyHint = tabLane.freeMarkerCursor
|
||||
? L"在网格内单击以固定数据点"
|
||||
: L"开启「自由标记点选取」后单击网格可固定数据点";
|
||||
UiTextClipped(g, emptyHint, m_fonts->uiSmall,
|
||||
gd::RectF(layout.content.X, layout.content.Y, layout.content.Width,
|
||||
groupRowHeight),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
@@ -8132,23 +8168,35 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 固定点条目(五列,列起点由布局给定 ⇒ 纵向严格对齐)----
|
||||
// ---- 固定点条目(六列,列起点由布局给定 ⇒ 纵向严格对齐)----
|
||||
// ⚠️ 取值与各列文本来自**与视界无关的内容缓存**(`FreeMarkerContents`):
|
||||
// 大量固定点时逐帧重新采样会让"拖光标 / 平移视界"明显变卡。
|
||||
const std::vector<FreeMarkerContentEntry>& contents =
|
||||
FreeMarkerContents(static_cast<int>(index));
|
||||
// 五列的**实测宽度**:含滚出视界的条目,保证列宽稳定、文本不被省略。
|
||||
// 六列的**实测宽度**:含滚出视界的条目,保证列宽稳定、文本不被省略。
|
||||
// 存回 `Lane` 供下一步的布局(`EdgePanelWidth` / `EdgePanelLayoutFor`)使用。
|
||||
{
|
||||
float widest[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
float widest[6] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const auto measure = [this, &widest](std::size_t column, const std::wstring& text) {
|
||||
if (text.empty() || column >= 5)
|
||||
if (text.empty() || column >= 6)
|
||||
{
|
||||
return;
|
||||
}
|
||||
widest[column] = std::max(widest[column],
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, text));
|
||||
};
|
||||
// 「序号」列:取本网格所有分组里最大的序号文本宽度。
|
||||
std::size_t maxSerial = 0;
|
||||
std::vector<std::size_t> serialOrder;
|
||||
for (const SignalRef& ref : tabLane.signals)
|
||||
{
|
||||
SortedFreeMarkerIndices(tabLane, ref, serialOrder);
|
||||
maxSerial = std::max(maxSerial, serialOrder.size());
|
||||
}
|
||||
if (maxSerial > 0)
|
||||
{
|
||||
measure(EdgePanelLayout::MARKER_COL_SERIAL, std::to_wstring(maxSerial));
|
||||
}
|
||||
for (const FreeMarkerContentEntry& entry : contents)
|
||||
{
|
||||
measure(EdgePanelLayout::MARKER_COL_VALUE, entry.valueText);
|
||||
@@ -8157,9 +8205,9 @@ namespace jdi
|
||||
measure(EdgePanelLayout::MARKER_COL_TIME, entry.timeText);
|
||||
measure(EdgePanelLayout::MARKER_COL_DT, entry.deltaTimeText);
|
||||
}
|
||||
std::array<float, 5>& stored =
|
||||
std::array<float, 6>& stored =
|
||||
tabLanes[index].freeMarkerColumnWidths;
|
||||
for (std::size_t column = 0; column < 5; ++column)
|
||||
for (std::size_t column = 0; column < 6; ++column)
|
||||
{
|
||||
// +2px 余量:`DrawTextW` 的 `DT_END_ELLIPSIS` 对"刚好装满"的文本
|
||||
// 有时仍会截成省略号(亚像素/字形伸部),留一点余量可彻底避免。
|
||||
@@ -8171,6 +8219,31 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
}
|
||||
// 列分隔竖线:在每列右边界(加上半个列间隙)画一条淡竖线,
|
||||
// 让「序号 / 标记值 / 枚举文本 / 增量 / 时间点 / 时间间隔」有明显的列分界
|
||||
//(用户 2026-10-23 要求)。最后一列不画(它右边紧接行尾 ✕)。
|
||||
const auto paintColumnSeparators = [this, &g, &layout](float y, float height) {
|
||||
int lastVisible = -1;
|
||||
for (int column = 0; column < 6; ++column)
|
||||
{
|
||||
if (layout.markerColumns[column].width > 0.0f)
|
||||
{
|
||||
lastVisible = column;
|
||||
}
|
||||
}
|
||||
for (int column = 0; column < lastVisible; ++column)
|
||||
{
|
||||
const EdgePanelLayout::MarkerColumn& cell = layout.markerColumns[column];
|
||||
if (cell.width <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float lineX =
|
||||
cell.left + cell.width + kFreeMarkerColumnGap * m_scale * 0.5f;
|
||||
UiDrawLine(g, lineX, y + 2.0f * m_scale, lineX, y + height - 2.0f * m_scale,
|
||||
WithAlpha(m_theme->borderStrong, 0x6C), 1.0f);
|
||||
}
|
||||
};
|
||||
for (const EdgePanelLayout::MarkerRow& markerRow : layout.markerRows)
|
||||
{
|
||||
if (markerRow.itemIndex >= contents.size())
|
||||
@@ -8213,6 +8286,8 @@ namespace jdi
|
||||
UiTextClipped(g, text, m_fonts->monoSmall, columnRect(column), color,
|
||||
TextAlign::LEFT, layout.content);
|
||||
};
|
||||
paintCell(EdgePanelLayout::MARKER_COL_SERIAL, std::to_wstring(markerRow.serial),
|
||||
m_theme->textMuted);
|
||||
paintCell(EdgePanelLayout::MARKER_COL_VALUE, entry.valueText,
|
||||
m_theme->textStrong);
|
||||
if (layout.markerHasEnumColumn)
|
||||
@@ -8225,6 +8300,9 @@ namespace jdi
|
||||
paintCell(EdgePanelLayout::MARKER_COL_DT, entry.deltaTimeText,
|
||||
m_theme->textMuted);
|
||||
|
||||
// 列分隔竖线(用户 2026-10-23 要求:各列之间要有明显分界)。
|
||||
paintColumnSeparators(markerRow.rect.Y, markerRow.rect.Height);
|
||||
|
||||
// 行尾 ✕:移除该固定点(与之前一致)。
|
||||
const gd::RectF& closeRect = markerRow.close;
|
||||
const bool hot = (m_freeMarkerPanelHoverLane == static_cast<int>(index) &&
|
||||
@@ -8258,7 +8336,7 @@ namespace jdi
|
||||
group.tableHeader.X + group.tableHeader.Width,
|
||||
group.tableHeader.Y + group.tableHeader.Height - 1.0f,
|
||||
WithAlpha(m_theme->borderStrong, 0x99), 1.0f);
|
||||
for (int column = 0; column < 5; ++column)
|
||||
for (int column = 0; column < 6; ++column)
|
||||
{
|
||||
const EdgePanelLayout::MarkerColumn& cell =
|
||||
layout.markerColumns[column];
|
||||
@@ -8271,6 +8349,8 @@ namespace jdi
|
||||
group.tableHeader.Height),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
}
|
||||
// 表头的列分隔竖线(与数据行同一套列边界)。
|
||||
paintColumnSeparators(group.tableHeader.Y, group.tableHeader.Height);
|
||||
}
|
||||
|
||||
// ---- 吸顶的分组栏(最后画:压住向上滚动的条目)----
|
||||
@@ -8293,7 +8373,7 @@ namespace jdi
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
|
||||
// ---- 「标记透明度」滑动条(仅「光标选取模式」激活时出现,固定在按钮上方)----
|
||||
// ---- 「标记透明度」滑动条(固定在「自由标记点选取」按钮上方)----
|
||||
if (layout.opacityTrack.Width > 0.0f)
|
||||
{
|
||||
UiText(g, kFreeMarkerOpacityLabel, m_fonts->uiSmall,
|
||||
@@ -8350,32 +8430,51 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 逐条目面板底部按钮行:「构建为新信号」(悬停高亮);
|
||||
// 「自由标记」面板的按钮是「光标选取模式」的**切换类按钮**
|
||||
//(交互外观与工具栏的切换按钮一致:激活时浅强调色底 + 强调描边 + 强文本色)。
|
||||
if (hasButton || freeMarker)
|
||||
{
|
||||
const gd::RectF& buttonRect = layout.button;
|
||||
if (buttonRect.Width > 0.0f)
|
||||
// 「自由标记」面板的底部固定区有三行:
|
||||
// ①「标记透明度」滑块(画在上面);
|
||||
// ②「自由标记点选取」切换按钮;
|
||||
// ③「显示增量 | 显示枚举文本」两个开关(用户 2026-10-23 要求)。
|
||||
// 三者都是切换类按钮外观,与工具栏的切换按钮一致
|
||||
//(激活时浅强调色底 + 强调描边 + 强文本色)。
|
||||
const auto paintToggleButton = [this, &g](const gd::RectF& buttonRect,
|
||||
const wchar_t* label, bool checked) {
|
||||
if (buttonRect.Width <= 0.0f)
|
||||
{
|
||||
const gd::RectF inner(buttonRect.X, buttonRect.Y + 2.0f * m_scale,
|
||||
buttonRect.Width, buttonRect.Height - 4.0f * m_scale);
|
||||
const bool hot =
|
||||
(m_dragMode != PlotDragMode::EDGE_PANEL_MOVE) &&
|
||||
UiPointInRect(inner, static_cast<float>(m_mouseX),
|
||||
static_cast<float>(m_mouseY));
|
||||
const bool checked = freeMarker && tabLane.freeMarkerCursor;
|
||||
const std::uint32_t background =
|
||||
checked ? (hot ? MixColor(m_theme->accentSoft, m_theme->accent, 0.16)
|
||||
: m_theme->accentSoft)
|
||||
: (hot ? m_theme->hover : m_theme->cardBg);
|
||||
UiFillRoundRect(g, inner, 4.0f * m_scale, background);
|
||||
UiDrawRoundRect(g, inner, 4.0f * m_scale,
|
||||
checked ? m_theme->accent : m_theme->border, 1.2f);
|
||||
const std::wstring label = freeMarker ? std::wstring(kFreeMarkerCursorModeLabel)
|
||||
: std::wstring(kBuildSignalLabel);
|
||||
UiText(g, label, m_fonts->uiSmall, inner,
|
||||
checked ? m_theme->textStrong : m_theme->text, TextAlign::CENTER);
|
||||
return;
|
||||
}
|
||||
const gd::RectF inner(buttonRect.X, buttonRect.Y + 2.0f * m_scale,
|
||||
buttonRect.Width, buttonRect.Height - 4.0f * m_scale);
|
||||
const bool hot = (m_dragMode != PlotDragMode::EDGE_PANEL_MOVE) &&
|
||||
UiPointInRect(inner, static_cast<float>(m_mouseX),
|
||||
static_cast<float>(m_mouseY));
|
||||
const std::uint32_t background =
|
||||
checked ? (hot ? MixColor(m_theme->accentSoft, m_theme->accent, 0.16)
|
||||
: m_theme->accentSoft)
|
||||
: (hot ? m_theme->hover : m_theme->cardBg);
|
||||
UiFillRoundRect(g, inner, 4.0f * m_scale, background);
|
||||
UiDrawRoundRect(g, inner, 4.0f * m_scale, checked ? m_theme->accent : m_theme->border,
|
||||
1.2f);
|
||||
// 窄按钮(两个开关各占半行)里文本可能超宽 ⇒ 省略,不允许画到按钮外。
|
||||
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, label);
|
||||
const std::wstring text =
|
||||
(labelWidth <= inner.Width - 6.0f * m_scale)
|
||||
? std::wstring(label)
|
||||
: UiEllipsize(label, m_fonts->uiSmall, inner.Width - 6.0f * m_scale);
|
||||
UiText(g, text, m_fonts->uiSmall, inner,
|
||||
checked ? m_theme->textStrong : m_theme->text, TextAlign::CENTER);
|
||||
};
|
||||
if (hasButton)
|
||||
{
|
||||
paintToggleButton(layout.button, kBuildSignalLabel, false);
|
||||
}
|
||||
if (freeMarker)
|
||||
{
|
||||
paintToggleButton(layout.button, kFreeMarkerCursorModeLabel,
|
||||
tabLane.freeMarkerCursor);
|
||||
paintToggleButton(layout.showDelta, kFreeMarkerShowDeltaLabel,
|
||||
tabLane.freeMarkerShowDelta);
|
||||
paintToggleButton(layout.showEnum, kFreeMarkerShowEnumLabel,
|
||||
tabLane.freeMarkerShowEnum);
|
||||
}
|
||||
} // if (!layout.collapsed)
|
||||
|
||||
@@ -8647,8 +8746,15 @@ namespace jdi
|
||||
const LaneVisual& lane = m_lanes[static_cast<std::size_t>(laneIndex)];
|
||||
// ⚠️ 「选区放大」模式下**仍保留悬停高亮**,但该模式下拖动是框选、不是拖信号
|
||||
// ⇒ 提示文案去掉“按住拖拽可移入其它网格”的尾缀,只提示信号名(用户 2026-10-17 要求)。
|
||||
// ⚠️ 同理(用户 2026-10-23 要求):该网格开启「自由标记点选取」时,左键专用于固定
|
||||
// 标记点、拖动也不再是拖信号 ⇒ 提示只显示信号名,不再提示可以拖拽。
|
||||
const std::vector<Lane>& tabLanes = m_tab->Lanes();
|
||||
const bool pickMode =
|
||||
laneIndex < static_cast<int>(tabLanes.size()) &&
|
||||
tabLanes[static_cast<std::size_t>(laneIndex)].freeMarker &&
|
||||
tabLanes[static_cast<std::size_t>(laneIndex)].freeMarkerCursor;
|
||||
const std::wstring text =
|
||||
(Tab().ZoomMode() == PlotZoomMode::SELECT)
|
||||
(Tab().ZoomMode() == PlotZoomMode::SELECT || pickMode)
|
||||
? resolved.data->name
|
||||
: (resolved.data->name + L" · 按住拖拽可移入其它网格");
|
||||
|
||||
|
||||
File renamed without changes.
Reference in new issue
Block a user