一、命名(用户要求) - 实验性功能里的「阶数 N」全部改为「迭代次数 N」(面板滑块标签、均值滤波/中心轴 的数值框标签、构建提示语)。 - 相关标识符与函数参数统一改名:order → iterations(Lane::MeanFilterItem::iterations、 Lane::weakBoostIterations、ComputeWeakWaveBoost/ComputeMovingAverageIterated 的形参、 MeanFilterCurveFor/WeakBoostCurveFor/WeakBoostValueAt/EdgeCenterCountsFor、 各曲线缓存项的字段、EdgePanelLayout::rowIterations/sliderIterations、 PlotEditTarget::MEAN_FILTER_ITERATIONS/EDGE_CENTER_ITERATIONS、 PlotDragMode::BOOST_ITERATIONS、kWeakBoostIterationsMin/Max/Label、 DerivedSignalRequest::iterations 等)。网格拖拽重排里的局部 order 为避免歧义 改名为 laneOrder;`std::memory_order_*`、`rowOrder`→`rowIterations` 一并核对无误。 - 导出信号尾缀统一为「N次迭代」: `<原信号> (N点均值 M次迭代)`、`<原信号> (弱波增强 X% M次迭代)`; 弱波增强此前缺失该信息,现已补上(两种派生信号都恒定带迭代次数)。 状态提示语同步为「… M 次迭代信号。」。 二、越界三角与光标读数的样式统一(用户要求) - 新增 `kMarkerTriangleGap`:网格内数据点越界时,方向三角与数值标注之间**留出间隙** (此前三角与胶囊贴在一起,看起来像胶囊上多出的尖角);中心轴/均值滤波/弱波增强的 派生取值标注同样处理。 - 光标时间读数(以及双光标 Δt 读数)改用与网格内数据点**完全一致**的越界样式: 三角画在胶囊**外侧**、尖端朝外、与胶囊之间留出同样的间隙;为此把读数胶囊在越界一侧 向内让出「三角深度 + 间隙」。旧的"三角压在胶囊上沿"的画法以及为它额外留出的叠放 间距(kCursorTipStackGap)一并删除。 三、大文件放大后"平移 / 改配色非常卡"的根因与修复(用户要求,流畅性优先) 用离屏探针(`.preflight/perf3`)在 VERY_BIG_DATA(1.77GB、2580 万采样 × 5 信号、 1224×796 绘图区)上先量化,实测到的三个根因: 1. **包络带"梳状化"**:当信号的振荡周期与像素列宽度相当时(编码器方波在 2ms/大格下 约 3 个采样点一列),逐列包络会在"全高"与"极窄"之间交替——实测 1224 列里有 988 处 全高跳变。这种多边形让 GDI+ 的扫描线填充耗时暴涨:同一条 2448 顶点的路径 **27.6ms → 1.1ms(±1 列极值扩展后快 25 倍)**。这正是"某些放大等级下平移特别卡" (实测 2ms/大格平移 87ms,其余等级 17~20ms)的原因。 → `EmitEnvelopeBand` 在拼装包络带时对列窗口做 ±1 列的极值扩展(纵向最多各多 1 像素, 肉眼不可辨),一次性消除该病理。 2. **派生预览曲线按折线逐段描边**:均值滤波/弱波增强曲线在可见采样数超过像素列数时 仍按"每 2 像素一个顶点"的抗锯齿折线绘制,而这类曲线常叠在方波上(大量近垂直跳变), 实测单条 12~42ms,5 个网格合计上百毫秒。 → 稠密时改用**逐像素列的包络带**(复用 `EmitEnvelopeBand`)一次 FillPath,实测 0.56ms / 1.53ms(分别快 22 倍 / 28 倍),视觉与细折线一致(列宽就是 1 像素)。 3. **悬停光晕用宽笔描整条折线**:实测悬停一帧多花 35ms。 → 包络带模式下改为"把带整体外扩后填充一次"(`PaintBandHalo`),悬停帧恢复正常。 另外修掉两个相关的固定开销: - `UiMeasureTextWidth` 增加 (字体, 文本) 记忆化缓存:它是布局代码的热点(浮动面板几何 按「网格数 × 面板种类」重复计算,每帧重复测量同一批字符串成百上千次),实测单个面板 的绘制因此从 3.2ms 降到 1ms 量级。字体重建(DPI 变化)时由 `UiClearMeasureCache` 作废。 实测(VERY_BIG_DATA 全选 5 信号,绘图区全重绘 = 平移一帧): - 20ms/大格(用户报告的场景):平移 20.1ms → **11.3ms**; - 2ms/大格(此前最糟):平移 86.7ms → **12.3ms**; - 网格 1 开弱波增强:63.9ms → **17.6ms**;5 个网格全开:136.6ms → **31.9ms**; - 悬停信号:整帧 49.6ms → **15.5ms**(与不悬停持平,光晕几乎免费)。 仍存的已知热点:0.5ms/大格附近(可见约 1000 个采样点、走**逐点描边**路径)平移约 28~39ms —— 该区间采样比像素还稀疏、无法用包络带替代,且用户明确要求保留抗锯齿, 本次未动。 四、验证 - 离屏 harness(窗口 SW_HIDE、不占用鼠标/键盘/屏幕)**355 项检查全部通过**, 新增 `[AD]`:越界读数胶囊向内让位、三角在外侧、三角与胶囊间有真实间隙、 胶囊上方不再有内容;取值标注与三角之间保留 `kMarkerTriangleGap` 的间隙(用 散点模式排除波形干扰后逐像素断言)。`[X]/[Y]/[AA]/[AB]/[Z]` 等既有用例同步通过。 - 性能结论来自 `.preflight/perf3`(离屏探针 + 两套微基准),已随任务清理。
1852 lines
73 KiB
C++
1852 lines
73 KiB
C++
#ifndef __PLOT_VIEW_HPP__
|
||
#define __PLOT_VIEW_HPP__
|
||
|
||
#include <windows.h>
|
||
#include <objidl.h>
|
||
#include <gdiplus.h>
|
||
|
||
#include <deque>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "DataModel.hpp"
|
||
#include "PaintUtil.hpp"
|
||
#include "Theme.hpp"
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
/**
|
||
* @brief 绘图区拖拽交互模式。
|
||
*/
|
||
enum class PlotDragMode : int
|
||
{
|
||
NONE = 0, /**< 未拖拽。 */
|
||
PAN, /**< 常规模式:水平平移时间轴 + 纵向偏移当前网格幅值。 */
|
||
RUBBER_SELECT, /**< 选区放大:按拖拽方向决定横向(时间轴)或纵向(幅值轴)选区。 */
|
||
SCROLL_LANES, /**< 拖动网格滚动条。 */
|
||
CURSOR_MOVE, /**< 拖拽时间光标。 */
|
||
CURSOR_SPAN_MOVE, /**< 拖拽双光标差值标签:两条光标整体平移,间隔不变。 */
|
||
MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */
|
||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
||
BOOST_ITERATIONS, /**< 拖动「弱波增强」面板中的迭代次数 N 滑块。 */
|
||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 选区放大拖拽的轴向(由首次明显位移的方向决定)。
|
||
*/
|
||
enum class PlotRubberAxis : int
|
||
{
|
||
UNDECIDED = 0, /**< 尚未确定(拖拽刚刚开始)。 */
|
||
TIME = 1, /**< 横向拖拽:放大时间轴。 */
|
||
VALUE = 2 /**< 纵向拖拽:放大当前网格的幅值轴。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 绘图区内的内联数值输入目标。
|
||
*/
|
||
enum class PlotEditTarget : int
|
||
{
|
||
NONE = 0, /**< 未处于输入状态。 */
|
||
CURSOR_TIME, /**< 编辑时间光标位置。 */
|
||
CURSOR_SPAN, /**< 编辑双光标之间的时间差(前方光标不动,后方光标随之移动)。 */
|
||
LANE_Y_MAX, /**< 编辑某网格的 Y 轴最大值。 */
|
||
LANE_Y_MIN, /**< 编辑某网格的 Y 轴最小值。 */
|
||
SIGNAL_NAME, /**< 编辑图例中某信号的显示名称。 */
|
||
EDGE_CENTER_POINTS, /**< 编辑「中心轴边沿计数」的均值点数 X。 */
|
||
EDGE_CENTER_ITERATIONS, /**< 编辑「中心轴边沿计数」的迭代次数 N。 */
|
||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||
MEAN_FILTER_ITERATIONS /**< 编辑「均值滤波」的迭代次数 N。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 绘图区右键菜单命令标识(与信号面板的命令空间相互独立)。
|
||
*/
|
||
enum PlotMenuCommand : int
|
||
{
|
||
PLOT_CMD_AUTO_FIT_ALL = 1, /**< 全局切换全部网格的「选区 Y 轴自适应」。 */
|
||
PLOT_CMD_LANE_Y_MAX = 2, /**< 输入该网格 Y 轴最大值。 */
|
||
PLOT_CMD_LANE_Y_MIN = 3, /**< 输入该网格 Y 轴最小值。 */
|
||
PLOT_CMD_FIT_ALL = 4, /**< 全部网格与时间轴自适应。 */
|
||
PLOT_CMD_LANE_FIT_TIME = 5, /**< 时间轴自适应到本网格信号范围(按网格时间轴展开)。 */
|
||
PLOT_CMD_LANE_AUTO_VIEW = 6, /**< 切换本网格的「选区 Y 轴自适应」开关。 */
|
||
PLOT_CMD_LANE_EDGE_COUNT = 7, /**< 切换本网格的「实验性功能 → 边沿计数」开关。 */
|
||
PLOT_CMD_LANE_EDGE_CENTER = 8, /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */
|
||
PLOT_CMD_LANE_SPLIT = 9, /**< 拆分当前网格:把网格内信号按个拆成独立网格并顺次下移编号。 */
|
||
PLOT_CMD_LANE_MEAN_FILTER = 10, /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */
|
||
PLOT_CMD_LANE_WEAK_BOOST = 11 /**< 切换本网格的「实验性功能 → 弱波增强」开关。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 实验性功能浮动面板的种类。
|
||
*/
|
||
enum class EdgePanelKind : int
|
||
{
|
||
COUNT = 0, /**< 边沿计数。 */
|
||
CENTER = 1, /**< 边沿计数(中心轴)。 */
|
||
MEAN = 2, /**< 均值滤波。 */
|
||
BOOST = 3 /**< 弱波增强。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 波形绘图区:网格、时间轴、光标、缩放、自适应与信号拖拽。
|
||
*
|
||
* 绘图区同时依赖数据层(DataSet,只读)与当前标签页配置(TabConfig,可写),
|
||
* 视图范围、网格滚动与时间光标均保存在标签页配置中,因此各标签页互不干扰。
|
||
*/
|
||
class PlotView
|
||
{
|
||
public:
|
||
PlotView();
|
||
|
||
/**
|
||
* @brief 析构:释放拖拽静态场景缓存的离屏位图。
|
||
*/
|
||
~PlotView();
|
||
|
||
/**
|
||
* @brief 设置宿主窗口句柄(用于剪贴板所有者)。
|
||
* @param [IN] host 宿主窗口句柄,允许为空。
|
||
*/
|
||
void SetHostWindow(HWND host) { m_host = host; }
|
||
|
||
/**
|
||
* @brief 绑定字体与主题资源。
|
||
* @param [IN] fonts 字体集合,需在视图生命周期内保持有效。
|
||
* @param [IN] theme 主题配色,需在视图生命周期内保持有效。
|
||
*/
|
||
void Setup(const UiFonts* fonts, const Theme* theme);
|
||
|
||
/**
|
||
* @brief 绑定数据层与当前标签页配置。
|
||
* @param [IN] dataSet 数据集合,允许为空。
|
||
* @param [IN] tab 当前标签页配置,允许为空。
|
||
*/
|
||
void SetContext(DataSet* dataSet, TabConfig* tab);
|
||
|
||
/**
|
||
* @brief 设置 DPI 缩放比例。
|
||
* @param [IN] scale 缩放比例(1.0 对应 96 DPI)。
|
||
*/
|
||
void SetScale(float scale);
|
||
|
||
/**
|
||
* @brief 按给定区域重新排布绘图区。
|
||
* @param [IN] area 可用区域。
|
||
*/
|
||
void Layout(const gd::RectF& area);
|
||
|
||
/**
|
||
* @brief 绘制绘图区。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void Paint(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 处理鼠标移动(含拖拽)。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnMouseMove(int x, int y);
|
||
|
||
/**
|
||
* @brief 处理鼠标左键按下。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnLButtonDown(int x, int y);
|
||
|
||
/**
|
||
* @brief 处理鼠标左键抬起。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnLButtonUp(int x, int y);
|
||
|
||
/**
|
||
* @brief 处理鼠标中键按下:与左键在常规模式下一致,进入自由平移(水平平移时间轴 +
|
||
* 纵向偏移当前网格幅值)。**任何缩放模式下都可用**,用于在不改变当前模式的
|
||
* 前提下自由查看区域。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnMButtonDown(int x, int y);
|
||
|
||
/**
|
||
* @brief 处理鼠标中键抬起:结束中键平移。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnMButtonUp();
|
||
|
||
/**
|
||
* @brief 处理鼠标滚轮(仅用于平移与网格滚动,不再承担缩放)。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @param [IN] delta 滚轮增量。
|
||
* @param [IN] shift 是否按下 Shift(水平平移时间轴)。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnMouseWheel(int delta, bool shift);
|
||
|
||
/**
|
||
* @brief 处理鼠标双击(命中时间光标时进入精确时间输入)。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnDoubleClick(int x, int y);
|
||
|
||
/**
|
||
* @brief 处理鼠标离开绘图区。
|
||
* @return 需要重绘返回 true。
|
||
*/
|
||
bool OnMouseLeave();
|
||
|
||
/**
|
||
* @brief 将时间轴自适应到指定网格内信号的时间范围。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 调整成功返回 true。
|
||
*/
|
||
bool FitTimeToLane(int laneIndex);
|
||
|
||
/**
|
||
* @brief 「拆分当前网格」:把该网格内的信号按个拆分成独立网格,新网格依次排在其下方。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 拆分成功返回 true。
|
||
*/
|
||
bool SplitLane(int laneIndex);
|
||
|
||
/**
|
||
* @brief 查询某网格内的信号数量(用于菜单项可用状态判定)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 信号数量,网格不存在时返回 0。
|
||
*/
|
||
std::size_t LaneSignalCount(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 依据当前显示时间段内的数据对该网格做 Y 轴自适应(“选区 Y 轴自适应”)。
|
||
*
|
||
* 与普通自适应(用整段数据的极值)不同:这里只看时间轴当前可见区间内的采样点,
|
||
* 因此放大到某段局部波形后可以快速把该段的幅值铺满整个网格。
|
||
*
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 调整成功返回 true(区间内无有效数据时返回 false)。
|
||
*/
|
||
bool AutoRangeLaneToView(int laneIndex);
|
||
|
||
/**
|
||
* @brief 对全部开启了「选区 Y 轴自适应」的网格做一次 Y 轴选区自适应。
|
||
*
|
||
* 时间轴选区放大后调用:只有开关开启的网格才自动匹配新的可见时间区间。
|
||
*/
|
||
void ApplySelectionAutoFit();
|
||
|
||
/**
|
||
* @brief 把时间吸附到距今最近的数据采样点。
|
||
*
|
||
* 仅在横向放大到采样点间距足够大(可精确分辨)时生效,
|
||
* 避免缩小时光标出现“抖动”难以微调。
|
||
*
|
||
* @param [IN] time 原始时间。
|
||
* @param [OUT] snapped 吸附后的时间。
|
||
* @return 成功吸附返回 true。
|
||
*/
|
||
bool SnapTimeToSample(double time, double& snapped) const;
|
||
|
||
/**
|
||
* @brief 计算指定文件中某一时刻附近采样点的时间间隔。
|
||
* @param [IN] file 目标文件。
|
||
* @param [IN] time 目标时刻。
|
||
* @param [OUT] pixels 间隔换算到屏幕上的像素宽度。
|
||
* @return 计算成功返回 true。
|
||
*/
|
||
bool SampleSpacingPixels(const DataFile& file, double time, double& pixels) const;
|
||
|
||
/**
|
||
* @brief 判断坐标是否落在绘图区范围内。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 落在范围内返回 true。
|
||
*/
|
||
bool HitTest(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 查询当前鼠标位置应使用的系统光标。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 系统光标句柄,未命中特殊区域返回 nullptr。
|
||
*/
|
||
HCURSOR QueryCursor(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 时间轴与幅值轴同时自适应到可见信号范围。
|
||
*/
|
||
void FitAll();
|
||
|
||
/**
|
||
* @brief 以给定倍率缩放时间轴(以视图中心为锚点)。
|
||
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
||
*/
|
||
void ZoomTimeStep(double factor);
|
||
|
||
/**
|
||
* @brief 按当前视图跨度的比例平移时间轴。
|
||
* @param [IN] fraction 平移比例,正值向右(时间增大)平移。
|
||
*/
|
||
void PanTimeByFraction(double fraction);
|
||
|
||
/**
|
||
* @brief 按网格内容高度的比例纵向滚动网格。
|
||
* @param [IN] fraction 滚动比例,正值向下滚动。
|
||
*/
|
||
void ScrollLanesByFraction(double fraction);
|
||
|
||
/**
|
||
* @brief 提示浮层是否仍在显示(含淡出阶段)。
|
||
*
|
||
* 宿主可在返回 true 时保持定时重绘,使提示能"停留后淡出"。
|
||
*
|
||
* @return 需要继续重绘返回 true。
|
||
*/
|
||
bool HintNeedsAnimation() const;
|
||
|
||
/**
|
||
* @brief 清除已启用的时间光标。
|
||
*/
|
||
void DisableCursor();
|
||
|
||
/**
|
||
* @brief 放弃当前正在进行的拖拽(缩放选区/平移/信号拖动),不改变视图范围。
|
||
* @param [IN] showHint 是否同时显示“已放弃选区”提示。
|
||
* @return 确实取消了某个拖拽返回 true。
|
||
*/
|
||
bool CancelDrag(bool showHint = false);
|
||
|
||
/**
|
||
* @brief 判断当前是否处于「选区放大」的框选过程中(供宿主处理右键放弃)。
|
||
* @return 正在框选返回 true。
|
||
*/
|
||
bool IsRubberSelecting() const { return m_dragMode == PlotDragMode::RUBBER_SELECT; }
|
||
|
||
/**
|
||
* @brief 退出全部缩放模式(选区放大),使工具栏缩放按钮回到未选中态。
|
||
* @return 之前处于缩放模式返回 true。
|
||
*/
|
||
bool ExitZoomModes();
|
||
|
||
/**
|
||
* @brief 使全部派生结果失效:折线顶点、边沿统计、中心平均值曲线与静态场景缓存。
|
||
*
|
||
* 数据层结构变化(加载/移除文件、信号增删)后信号引用与文件地址都可能改变,
|
||
* 必须整体失效,避免旧几何键与新的信号意外重合而取到过期结果。
|
||
*/
|
||
void InvalidateDerivedCaches();
|
||
|
||
/**
|
||
* @brief 三态循环切换时间光标:无 → 单光标 → 双光标 → 无。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool CycleCursorMode();
|
||
|
||
/**
|
||
* @brief 判断时间光标是否已启用(单/双光标均为启用)。
|
||
* @return 启用返回 true。
|
||
*/
|
||
bool CursorEnabled() const;
|
||
|
||
/**
|
||
* @brief 获取当前时间光标模式。
|
||
* @return 光标模式。
|
||
*/
|
||
PlotCursorMode CursorMode() const;
|
||
|
||
/**
|
||
* @brief 获取光标 1 对应的时间。
|
||
* @return 光标 1 时间。
|
||
*/
|
||
double CursorTime() const;
|
||
|
||
/**
|
||
* @brief 切换拖拽缩放模式(再次调用同一模式则取消)。
|
||
* @param [IN] mode 目标模式。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool ToggleZoomMode(PlotZoomMode mode);
|
||
|
||
/**
|
||
* @brief 获取当前拖拽缩放模式。
|
||
* @return 模式枚举。
|
||
*/
|
||
PlotZoomMode CurrentZoomMode() const;
|
||
|
||
/**
|
||
* @brief 查询坐标命中的网格下标(用于右键菜单)。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 网格下标,未命中返回 -1。
|
||
*/
|
||
int LaneIndexAt(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 判断某网格是否开启了「选区 Y 轴自适应」开关(菜单勾选标记)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 开启返回 true。
|
||
*/
|
||
bool LaneAutoFitToSelection(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 判断是否全部网格都开启了「选区 Y 轴自适应」(用于全局菜单项与工具栏按钮)。
|
||
* @return 存在网格且全部开启返回 true。
|
||
*/
|
||
bool AllLanesAutoFitToSelection() const;
|
||
|
||
/**
|
||
* @brief 切换某网格的「选区 Y 轴自适应」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 切换后的状态。
|
||
*/
|
||
bool ToggleLaneAutoFitToSelection(int laneIndex);
|
||
|
||
/**
|
||
* @brief 让某网格的「选区 Y 轴自适应」开关立即生效一次(打开时调用)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
*/
|
||
void ApplyAutoFitToSelectionNow(int laneIndex);
|
||
|
||
/**
|
||
* @brief 对全部网格的「选区 Y 轴自适应」开关立即生效一次(全局打开时调用)。
|
||
*/
|
||
void ApplyAllAutoFitToSelectionNow();
|
||
|
||
/**
|
||
* @brief 一次性设置全部网格的「选区 Y 轴自适应」开关(全局菜单项 / 工具栏按钮)。
|
||
* @param [IN] enabled 目标状态。
|
||
* @param [IN] applyNow 打开时是否立即按当前视图执行一次 Y 轴自适应。
|
||
* @return 任一网格状态发生变化返回 true。
|
||
*/
|
||
bool SetAllLanesAutoFitToSelection(bool enabled, bool applyNow = true);
|
||
|
||
/**
|
||
* @brief 判断某网格是否开启了「实验性功能 → 边沿计数」(菜单勾选标记)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 开启返回 true。
|
||
*/
|
||
bool LaneEdgeCount(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 设置某网格的「边沿计数」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] enabled 目标状态。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool SetLaneEdgeCount(int laneIndex, bool enabled);
|
||
|
||
/**
|
||
* @brief 切换某网格的「边沿计数」开关(开启时自动初始化横向光标位置与面板)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 切换后的状态。
|
||
*/
|
||
bool ToggleLaneEdgeCount(int laneIndex);
|
||
|
||
/**
|
||
* @brief 判断某网格是否开启了「实验性功能 → 边沿计数(中心轴)」。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 开启返回 true。
|
||
*/
|
||
bool LaneEdgeCenterCount(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 设置某网格的「边沿计数(中心轴)」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] enabled 目标状态。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool SetLaneEdgeCenterCount(int laneIndex, bool enabled);
|
||
|
||
/**
|
||
* @brief 切换某网格的「边沿计数(中心轴)」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 切换后的状态。
|
||
*/
|
||
bool ToggleLaneEdgeCenterCount(int laneIndex);
|
||
|
||
/**
|
||
* @brief 判断某网格是否开启了「实验性功能 → 均值滤波」。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 开启返回 true。
|
||
*/
|
||
bool LaneMeanFilter(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 设置某网格的「均值滤波」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] enabled 目标状态。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool SetLaneMeanFilter(int laneIndex, bool enabled);
|
||
|
||
/**
|
||
* @brief 切换某网格的「均值滤波」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 切换后的状态。
|
||
*/
|
||
bool ToggleLaneMeanFilter(int laneIndex);
|
||
|
||
/**
|
||
* @brief 「均值滤波 → 构建为新信号」的单个请求(一个被勾选的信号 + 自己的参数)。
|
||
*/
|
||
struct MeanFilterBuild
|
||
{
|
||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||
int points = 5; /**< 均值点数 X(滑平半径)。 */
|
||
int iterations = 1; /**< 迭代次数 N(至少 1)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 取出「构建为新信号」请求(由面板底部按钮触发)。
|
||
*
|
||
* 请求只描述「哪一网格、哪些被勾选的信号及其参数」,真正的信号构建与插入由宿主
|
||
* (Application)完成——数据层属于 DataSet,绘图区不能直接修改它。
|
||
*
|
||
* @param [OUT] laneIndex 触发该请求的网格下标。
|
||
* @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。
|
||
* @return 存在待处理请求返回 true(取出后清空)。
|
||
*/
|
||
bool TakePendingMeanFilterBuild(int& laneIndex, std::vector<MeanFilterBuild>& items);
|
||
|
||
/**
|
||
* @brief 「实验性功能 → 弱波增强」的「构建为新信号」单个请求
|
||
* (一个被勾选的信号 + 整个网格共用的增强等级)。
|
||
*/
|
||
struct WeakBoostBuild
|
||
{
|
||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 取出「弱波增强 → 构建为新信号」请求(由面板底部按钮触发)。
|
||
* @param [OUT] laneIndex 触发该请求的网格下标。
|
||
* @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。
|
||
* @return 存在待处理请求返回 true(取出后清空)。
|
||
*/
|
||
bool TakePendingWeakBoostBuild(int& laneIndex, std::vector<WeakBoostBuild>& items);
|
||
|
||
/**
|
||
* @brief 切换某网格的「弱波增强」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] enabled 是否开启。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool SetLaneWeakBoost(int laneIndex, bool enabled);
|
||
|
||
/**
|
||
* @brief 查询某网格的「弱波增强」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 已开启返回 true。
|
||
*/
|
||
bool LaneWeakBoost(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 切换某网格的「弱波增强」开关。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 切换后的状态。
|
||
*/
|
||
bool ToggleLaneWeakBoost(int laneIndex);
|
||
|
||
/**
|
||
* @brief 读取某网格的弱波增强等级(0–1)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 增强等级;网格不存在时返回 0.5。
|
||
*/
|
||
double LaneWeakBoostLevel(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 读取某网格的弱波增强迭代次数 N(1–10)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 迭代次数;网格不存在时返回 1。
|
||
*/
|
||
int LaneWeakBoostIterations(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 设置某网格的弱波增强迭代次数 N(1–10,超出范围会被夹取)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] iterations 迭代次数。
|
||
* @return 迭代次数发生变化返回 true。
|
||
*/
|
||
bool SetLaneWeakBoostIterations(int laneIndex, int iterations);
|
||
|
||
/**
|
||
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] level 增强等级。
|
||
* @return 等级发生变化返回 true。
|
||
*/
|
||
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
||
|
||
/**
|
||
* @brief 获取弱波增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||
*/
|
||
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent,
|
||
int iterations) const;
|
||
|
||
/**
|
||
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
||
* @param [IN] file 信号所属文件。
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||
* @param [IN] time 采样时刻。
|
||
* @param [OUT] value 输出取值。
|
||
* @return 取到有效值返回 true。
|
||
*/
|
||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int iterations,
|
||
double time, double& value) const;
|
||
|
||
/**
|
||
* @brief 切换某个信号在「弱波增强」面板中的预览勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 切换后的勾选状态。
|
||
*/
|
||
bool ToggleLaneWeakBoostSignal(int laneIndex, const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 查询某个信号在「弱波增强」面板中的预览勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 已勾选返回 true。
|
||
*/
|
||
bool LaneWeakBoostSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||
|
||
/**
|
||
* @brief 直接把文本解析为网格 Y 轴上限/下限并应用(供菜单内联输入框提交使用)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] editMax true 写上限,false 写下限。
|
||
* @param [IN] text 输入文本。
|
||
* @return 解析并应用成功返回 true。
|
||
*/
|
||
bool ApplyLaneValueText(int laneIndex, bool editMax, const std::wstring& text);
|
||
|
||
/**
|
||
* @brief 查询某网格当前的 Y 轴显示范围。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [OUT] yMin 输出下限。
|
||
* @param [OUT] yMax 输出上限。
|
||
* @return 网格下标合法返回 true。
|
||
*/
|
||
bool LaneRange(int laneIndex, double& yMin, double& yMax) const;
|
||
|
||
/**
|
||
* @brief 判断是否处于任意内联数值输入状态。
|
||
* @return 处于编辑态返回 true。
|
||
*/
|
||
bool IsEditing() const { return m_editTarget != PlotEditTarget::NONE; }
|
||
|
||
/**
|
||
* @brief 取出并清除待应用的信号重命名请求。
|
||
* @param [OUT] ref 被重命名的信号引用。
|
||
* @param [OUT] name 新的显示名称。
|
||
* @return 存在待处理请求返回 true。
|
||
* @note 绘图区只持有只读数据层,重命名需由宿主窗口落到 DataSet 上。
|
||
*/
|
||
bool TakePendingRename(SignalRef& ref, std::wstring& name);
|
||
|
||
/**
|
||
* @brief 处理编辑态下的字符输入。
|
||
* @param [IN] character 输入字符。
|
||
* @return 界面状态发生变化返回 true。
|
||
*/
|
||
bool OnEditChar(wchar_t character);
|
||
|
||
/**
|
||
* @brief 处理编辑态下的按键。
|
||
* @param [IN] key 虚拟键码。
|
||
* @return 界面状态发生变化返回 true。
|
||
*/
|
||
bool OnEditKey(WPARAM key);
|
||
|
||
/**
|
||
* @brief 编辑态下按下鼠标右键:取消选区并把插入符移到文本末尾。
|
||
* @return 发生改变返回 true;不在编辑态返回 false。
|
||
*/
|
||
bool CollapseEditSelectionToEnd();
|
||
|
||
/**
|
||
* @brief 编辑态下在输入框内按下鼠标左键:定位插入符并开始拖拽框选。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 命中输入框返回 true。
|
||
*/
|
||
bool OnEditMouseDown(int x, int y);
|
||
|
||
/**
|
||
* @brief 编辑态下拖拽鼠标:扩展选区。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @return 选区发生变化返回 true。
|
||
*/
|
||
bool OnEditMouseMove(int x);
|
||
|
||
/**
|
||
* @brief 编辑态下松开鼠标左键:结束拖拽框选。
|
||
* @return 之前处于拖拽中返回 true。
|
||
*/
|
||
bool OnEditMouseUp();
|
||
|
||
/**
|
||
* @brief 重新生成网格几何(可见性或网格划分变化后调用)。
|
||
*/
|
||
void RefreshLanes();
|
||
|
||
private:
|
||
/**
|
||
* @brief 单个网格的绘制信息。
|
||
*/
|
||
struct LaneVisual
|
||
{
|
||
gd::RectF rect; /**< 网格波形区域。 */
|
||
double yMin = 0.0; /**< 幅值下限。 */
|
||
double yMax = 1.0; /**< 幅值上限。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 已解析的信号视图:数据与显示配置的组合。
|
||
*/
|
||
struct ResolvedSignal
|
||
{
|
||
const SignalData* data = nullptr; /**< 信号数据(含全局共享的显示外观)。 */
|
||
const SignalStyle* style = nullptr; /**< 显示外观。 */
|
||
const DataFile* file = nullptr; /**< 所属文件。 */
|
||
bool visible = false; /**< 在当前标签页中是否可见。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 获取当前生效的标签页配置(未绑定时返回内部占位对象)。
|
||
* @return 标签页配置引用。
|
||
*/
|
||
TabConfig& Tab() { return (m_tab != nullptr) ? *m_tab : m_localTab; }
|
||
|
||
/**
|
||
* @brief 获取当前生效的标签页配置(只读)。
|
||
* @return 标签页配置引用。
|
||
*/
|
||
const TabConfig& Tab() const { return (m_tab != nullptr) ? *m_tab : m_localTab; }
|
||
|
||
/**
|
||
* @brief 判断是否存在可绘制内容(已加载数据且至少有一条可见信号)。
|
||
* @return 可绘制返回 true。
|
||
*/
|
||
bool HasDrawableContent() const;
|
||
|
||
/**
|
||
* @brief 计算当前视图应使用的时间范围。
|
||
* @param [OUT] low 时间下限。
|
||
* @param [OUT] high 时间上限。
|
||
*/
|
||
void TimeBounds(double& low, double& high) const;
|
||
|
||
/**
|
||
* @brief 解析信号引用,得到数据与显示配置。
|
||
* @param [IN] ref 信号引用。
|
||
* @return 解析结果,引用无效时各指针为空。
|
||
*/
|
||
ResolvedSignal ResolveSignal(const SignalRef& ref) const;
|
||
|
||
/**
|
||
* @brief 判断某网格内的可见信号是否来自多个文件。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 跨越多个文件返回 true。
|
||
*/
|
||
bool LaneSpansMultipleFiles(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 获取当前选中信号(跨标签页共享,切换视图后选中项保持一致)。
|
||
* @return 信号引用。
|
||
*/
|
||
const SignalRef& SelectedRef() const;
|
||
|
||
/**
|
||
* @brief 设置当前选中信号(跨标签页共享)。
|
||
* @param [IN] ref 信号引用。
|
||
*/
|
||
void SelectSignal(const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 重新计算各网格的布局与幅值范围。
|
||
*/
|
||
void BuildLaneVisuals();
|
||
|
||
/**
|
||
* @brief 限制网格滚动偏移量。
|
||
*/
|
||
void ClampLaneScroll();
|
||
|
||
/**
|
||
* @brief 将时间映射为绘图区横坐标。
|
||
* @param [IN] time 时间值。
|
||
* @return 横坐标。
|
||
*/
|
||
float TimeToX(double time) const;
|
||
|
||
/**
|
||
* @brief 将绘图区横坐标还原为时间。
|
||
* @param [IN] x 横坐标。
|
||
* @return 时间值。
|
||
*/
|
||
double XToTime(float x) const;
|
||
|
||
/**
|
||
* @brief 将幅值映射为纵坐标。
|
||
* @param [IN] value 幅值。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @return 纵坐标。
|
||
*/
|
||
float ValueToY(double value, const LaneVisual& lane) const;
|
||
|
||
/**
|
||
* @brief 将纵坐标还原为幅值。
|
||
* @param [IN] y 纵坐标。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @return 幅值。
|
||
*/
|
||
double YToValue(float y, const LaneVisual& lane) const;
|
||
|
||
/**
|
||
* @brief 查询指定纵坐标落在哪个网格内。
|
||
* @param [IN] y 纵坐标。
|
||
* @return 网格下标,未命中返回 -1。
|
||
*/
|
||
int LaneAtY(int y) const;
|
||
|
||
/**
|
||
* @brief 限制时间视图范围,避免过度平移到数据之外。
|
||
*/
|
||
void ClampTimeView();
|
||
|
||
/**
|
||
* @brief 以指定锚点缩放时间轴。
|
||
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
||
* @param [IN] anchorTime 锚点时间。
|
||
*/
|
||
void ZoomTimeAt(double factor, double anchorTime);
|
||
|
||
/**
|
||
* @brief 将时间轴缩放到指定的时间区间(拖拽选区释放时调用)。
|
||
* @param [IN] x0 选区起点横坐标。
|
||
* @param [IN] x1 选区终点横坐标。
|
||
* @return 产生了有效缩放返回 true。
|
||
*/
|
||
bool ApplyTimeSelection(float x0, float x1);
|
||
|
||
/**
|
||
* @brief 将某个网格的幅值轴缩放到指定区间(拖拽选区释放时调用)。
|
||
* @param [IN] laneIndex 目标网格下标。
|
||
* @param [IN] y0 选区起点纵坐标。
|
||
* @param [IN] y1 选区终点纵坐标。
|
||
* @return 产生了有效缩放返回 true。
|
||
*/
|
||
bool ApplyValueSelection(int laneIndex, float y0, float y1);
|
||
|
||
/**
|
||
* @brief 平移某网格的幅值轴(偏移波形显示位置)。
|
||
* @param [IN] laneIndex 目标网格下标。
|
||
* @param [IN] deltaValue 幅值偏移量。
|
||
*/
|
||
void ShiftLaneRange(int laneIndex, double deltaValue);
|
||
|
||
/**
|
||
* @brief 计算指定时间光标当前的横坐标。
|
||
* @param [IN] which 光标序号(1 或 2)。
|
||
* @return 横坐标。
|
||
*/
|
||
float CursorX(int which) const;
|
||
|
||
/**
|
||
* @brief 计算指定光标在屏幕上的**显示**横坐标(滞留在可视区外时贴到网格边缘)。
|
||
* @param [IN] which 光标序号(1 或 2)。
|
||
* @return 显示横坐标。
|
||
*/
|
||
float CursorDisplayX(int which) const;
|
||
|
||
/**
|
||
* @brief 计算指定光标时间读数标签的矩形。
|
||
* @param [IN] which 光标序号(1 或 2)。
|
||
* @return 标签矩形,位于绘图区底部(不遮挡网格内数据点标注)。
|
||
*/
|
||
gd::RectF CursorLabelRect(int which) const;
|
||
|
||
/**
|
||
* @brief 判断给定横坐标是否落在某条时间光标附近(允许拖拽的容差范围)。
|
||
* @param [IN] x 横坐标。
|
||
* @param [OUT] which 命中的光标序号(1 或 2),未命中时为 0。
|
||
* @return 命中返回 true。
|
||
*/
|
||
bool IsOnCursorLine(int x, int& which) const;
|
||
|
||
/**
|
||
* @brief 将两条光标的时间夹取到当前可见时间范围内。
|
||
*
|
||
* ⚠️ 现在**只在拖拽光标时**使用。视图变化(平移/放大/自适应)必须**不**移动光标:
|
||
* 光标允许滞留在网格可视区域之外,此时仅把它的取值/时间读数贴在网格边缘显示。
|
||
*
|
||
* @param [IN] which 要夹取的光标序号(1 或 2)。
|
||
*/
|
||
void ClampCursorToView(int which);
|
||
|
||
/**
|
||
* @brief 计算双光标之间的时间差(绝对值)。
|
||
* @return 时间差;非双光标模式返回 0。
|
||
*/
|
||
double CursorSpan() const;
|
||
|
||
/**
|
||
* @brief 判断指定光标是否滞留在可视区之外(时间读数被钉在网格边缘)。
|
||
* @param [IN] which 光标序号(1 或 2)。
|
||
* @param [OUT] offRight true 表示偏出**右**侧视界,false 表示偏出左侧。
|
||
* @return 越界返回 true;光标在视界内返回 false。
|
||
*/
|
||
bool CursorOffViewSide(int which, bool& offRight) const;
|
||
|
||
/**
|
||
* @brief 在读数胶囊上方绘制"数据在视界之外"的方向三角指示。
|
||
* @param [IN] labelRect 胶囊矩形(三角与其水平居中对齐)。
|
||
* @param [IN] offRight true 表示指向右侧(光标在右视界之外)。
|
||
* @param [IN] color 三角颜色(与胶囊同色)。
|
||
*/
|
||
void PaintCursorOffViewTip(gd::Graphics& g, const gd::RectF& labelRect, bool offRight,
|
||
std::uint32_t color);
|
||
|
||
/**
|
||
* @brief 计算双光标差值标签的矩形(时间轴上方、两条时间读数的中间位置)。
|
||
* @param [OUT] insideAxis 为 true 时表示两条光标过近,标签落在主时间轴内部。
|
||
* @return 标签矩形;非双光标模式返回空矩形。
|
||
*/
|
||
gd::RectF CursorSpanRect(bool& insideAxis) const;
|
||
/**
|
||
* @brief 获取指定光标对应的显示颜色。
|
||
* @param [IN] which 光标序号(1 或 2)。
|
||
* @return 颜色。
|
||
*/
|
||
std::uint32_t CursorColor() const;
|
||
|
||
/**
|
||
* @brief 设置并显示一条临时提示(在绘图区底部以浮层方式绘制,短暂停留后淡出)。
|
||
* @param [IN] text 提示文本;传空串则立即清除提示。
|
||
*/
|
||
void SetHint(const std::wstring& text);
|
||
|
||
/**
|
||
* @brief 设置一条"粘滞"提示:直到 `ReleaseHint()` 为止不会淡出。
|
||
*
|
||
* 用于"拖动过程中维持显示"的场景(例如选区拖拽时的右键/ESC 说明)。
|
||
*
|
||
* @param [IN] text 提示文本。
|
||
*/
|
||
void SetHintSticky(const std::wstring& text);
|
||
|
||
/**
|
||
* @brief 结束粘滞状态,并从这里开始重新计时(停留后淡出)。
|
||
*/
|
||
void ReleaseHint();
|
||
|
||
/**
|
||
* @brief 判断坐标是否落在某条光标的**时间读数标签**上。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @param [OUT] which 命中的光标序号(1 或 2),未命中时为 0。
|
||
* @return 命中返回 true。
|
||
*/
|
||
bool IsOnCursorTimeLabel(int x, int y, int& which) const;
|
||
|
||
/**
|
||
* @brief 判断坐标是否落在双光标差值标签上。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 命中返回 true。
|
||
*/
|
||
bool IsOnCursorSpanLabel(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 画文字时给底色胶囊加一层细描边,提升与背景/曲线的对比度。
|
||
* @param [IN] rect 胶囊矩形。
|
||
* @param [IN] radius 圆角半径。
|
||
* @param [IN] outlineColor 描边色(由 `LabelInkFor` 与文字色一并算出的反相色)。
|
||
*/
|
||
void DrawLabelOutline(gd::Graphics& g, const gd::RectF& rect, float radius,
|
||
std::uint32_t outlineColor);
|
||
|
||
/**
|
||
* @brief 精确检测鼠标位置是否命中某条可见信号曲线。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 命中的信号引用,未命中返回无效引用。
|
||
*/
|
||
SignalRef HitTestSignal(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 计算当前内联输入框的矩形(依编辑目标而定)。
|
||
* @return 输入框矩形,未处于编辑态时返回空矩形。
|
||
*/
|
||
gd::RectF EditFieldRect() const;
|
||
|
||
/**
|
||
* @brief 以给定目标与初始文本进入内联输入状态。
|
||
* @param [IN] target 编辑目标。
|
||
* @param [IN] laneIndex 目标网格下标(光标编辑时忽略)。
|
||
* @param [IN] initialText 初始文本。
|
||
* @return 进入成功返回 true。
|
||
*/
|
||
bool BeginEdit(PlotEditTarget target, int laneIndex, const std::wstring& initialText,
|
||
const gd::RectF& anchorRect = gd::RectF());
|
||
|
||
/**
|
||
* @brief 结束内联输入。
|
||
* @param [IN] commit 是否提交当前输入。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool EndEdit(bool commit);
|
||
|
||
/**
|
||
* @brief 构建单条信号在当前视图下的折线点序列。
|
||
* @param [IN] resolved 已解析的信号视图。
|
||
* @param [IN] lane 所属网格绘制信息。
|
||
* @param [OUT] points 输出点序列。
|
||
* @param [OUT] breaks 输出每段折线的起始下标(用于处理缺失值断线)。
|
||
*/
|
||
void BuildPolyline(const ResolvedSignal& resolved, const LaneVisual& lane,
|
||
std::vector<gd::PointF>& points, std::vector<std::size_t>& breaks,
|
||
std::vector<gd::PointF>& band, std::vector<std::size_t>& bandBreaks,
|
||
bool& bandMode) const;
|
||
|
||
/**
|
||
* @brief 绘制单个网格。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @param [IN] timeTicks 时间刻度序列。
|
||
*/
|
||
void PaintLane(gd::Graphics& g, int laneIndex, const LaneVisual& lane,
|
||
const std::vector<double>& timeTicks);
|
||
|
||
/**
|
||
* @brief 绘制底部时间轴。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] timeTicks 时间刻度序列。
|
||
* @param [IN] timeStep 时间刻度步长。
|
||
*/
|
||
void PaintTimeAxis(gd::Graphics& g, const std::vector<double>& timeTicks, double timeStep);
|
||
|
||
/**
|
||
* @brief 绘制时间光标、选区及其读数。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] lanes 网格绘制信息集合。
|
||
*/
|
||
void PaintCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes);
|
||
|
||
/**
|
||
* @brief 绘制「边沿计数」的横向光标(含两端把手、左侧幅值读数)与统计浮动面板。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] lanes 网格绘制信息集合。
|
||
*/
|
||
void PaintEdgeCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes);
|
||
|
||
/**
|
||
* @brief 判断坐标是否命中某网格的「边沿计数」横向光标线。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @param [OUT] laneIndex 命中的网格下标。
|
||
* @return 命中返回 true。
|
||
*/
|
||
bool IsOnEdgeCursorLine(int x, int y, int& laneIndex) const;
|
||
|
||
/**
|
||
* @brief 实验性功能浮动面板的几何布局(三类面板共用同一套样式)。
|
||
*/
|
||
struct EdgePanelLayout
|
||
{
|
||
gd::RectF panel; /**< 面板整体矩形。 */
|
||
gd::RectF title; /**< 标题栏(**唯一可拖拽区域**)。 */
|
||
gd::RectF close; /**< ✕ 关闭按钮。 */
|
||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||
gd::RectF sliderIterations; /**< 「弱波增强」迭代次数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||
/**
|
||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||
*
|
||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为第二排的两个输入框;
|
||
* `rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||
* 未勾选的条目对应的矩形均为空。
|
||
*/
|
||
std::vector<gd::RectF> rowChecks;
|
||
std::vector<gd::RectF> rowPoints;
|
||
std::vector<gd::RectF> rowIterations;
|
||
std::vector<gd::RectF> rowStats;
|
||
std::vector<SignalRef> rowRefs;
|
||
};
|
||
|
||
/**
|
||
* @brief 计算某个实验性功能面板的布局。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] kind 面板种类。
|
||
* @return 布局;面板未展开时各项均为空矩形。
|
||
*/
|
||
EdgePanelLayout EdgePanelLayoutFor(int laneIndex, EdgePanelKind kind) const;
|
||
|
||
/**
|
||
* @brief 获取多阶(迭代)均值滤波曲线,结果带缓存。
|
||
*
|
||
* 语义:以「均值点数 X」作滑平半径,对输入重复做 `iterations` 次滑动平均。
|
||
* 「实验性功能 → 均值滤波」与「中心轴边沿计数」共用本函数,二者曲线因此逐位一致。
|
||
*
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] points 均值点数 X(滑平半径)。
|
||
* @param [IN] iterations 迭代次数 N(至少 1)。
|
||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||
*/
|
||
const std::vector<float>& MeanFilterCurveFor(const SignalData& signal, int points,
|
||
int iterations) const;
|
||
|
||
/**
|
||
* @brief 取某个时刻的多次迭代均值滤波曲线取值(用于纵向光标的取值标注)。
|
||
* @param [IN] file 信号所属文件。
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] points 均值点数 X。
|
||
* @param [IN] iterations 迭代次数 N。
|
||
* @param [IN] time 采样时刻。
|
||
* @param [OUT] value 输出取值。
|
||
* @return 取到有效值返回 true。
|
||
*/
|
||
bool MeanFilterValueAt(const DataFile& file, const SignalData& signal, int points, int iterations,
|
||
double time, double& value) const;
|
||
|
||
/**
|
||
* @brief 切换某个信号在「均值滤波」面板中的预览勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 切换后的勾选状态。
|
||
*/
|
||
bool ToggleLaneMeanFilterSignal(int laneIndex, const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 查询某个信号在「均值滤波」面板中的预览勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 已勾选返回 true。
|
||
*/
|
||
bool LaneMeanFilterSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||
|
||
/**
|
||
* @brief 统计信号穿越“均值滤波曲线”的上/下边沿数量,结果带缓存。
|
||
*
|
||
* 曲线由「均值点数 X + 迭代次数 N」确定,与「实验性功能 → 均值滤波」的预览曲线逐位一致。
|
||
*
|
||
* @param [IN] file 信号所属文件。
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] points 均值点数 X。
|
||
* @param [IN] iterations 迭代次数 N。
|
||
* @param [IN] hasRange 是否只统计某个时间区间(双光标模式下为光标区间)。
|
||
* @param [IN] rangeMin 区间左端点时间。
|
||
* @param [IN] rangeMax 区间右端点时间。
|
||
* @param [OUT] rising 上升沿数量(信号从下往上越过均值滤波曲线)。
|
||
* @param [OUT] falling 下降沿数量(信号从上往下穿过均值滤波曲线)。
|
||
*/
|
||
void EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int points, int iterations,
|
||
bool hasRange, double rangeMin, double rangeMax, std::size_t& rising,
|
||
std::size_t& falling) const;
|
||
|
||
/**
|
||
* @brief 切换某个信号在「中心轴边沿计数」面板中的勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 切换后的勾选状态。
|
||
*/
|
||
bool ToggleLaneEdgeCenterSignal(int laneIndex, const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 查询某个信号在「中心轴边沿计数」面板中的勾选状态。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] ref 目标信号。
|
||
* @return 已勾选返回 true。
|
||
*/
|
||
bool LaneEdgeCenterSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||
|
||
/**
|
||
* @brief 绘制一段派生曲线(中心轴均值滤波 / 多次迭代均值滤波共用同一实现)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] resolved 信号视图。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @param [IN] curve 曲线数值数组(与信号采样等长,缺失处为 NaN)。
|
||
*/
|
||
void PaintDerivedCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||
const std::vector<float>& curve) const;
|
||
|
||
/**
|
||
* @brief 绘制某条信号的多次迭代均值滤波曲线(与中心平均值曲线同一配色派生规则)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] resolved 信号视图。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @param [IN] points 均值点数 X(滑平半径)。
|
||
* @param [IN] iterations 迭代次数 N(至少 1)。
|
||
*/
|
||
void PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||
int points, int iterations) const;
|
||
|
||
/**
|
||
* @brief 绘制某条信号的弱波增强曲线(与均值滤波曲线同一配色派生规则)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] resolved 信号视图。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||
*/
|
||
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||
int levelPercent, int iterations) const;
|
||
|
||
/**
|
||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||
*/
|
||
struct DataMarker
|
||
{
|
||
float x = 0.0f; /**< 标记锚点横坐标(已夹在可视区内)。 */
|
||
float y = 0.0f; /**< 标记锚点纵坐标(已夹在可视区内)。 */
|
||
bool offLeft = false; /**< 数据点位于左侧视界之外。 */
|
||
bool offRight = false; /**< 数据点位于右侧视界之外。 */
|
||
bool offUp = false; /**< 数据点位于上方视界之外。 */
|
||
bool offDown = false; /**< 数据点位于下方视界之外。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 计算数据点标记:把越界的数据点"钉"在网格可见边缘,并记录越界方向。
|
||
* @param [IN] rawX 数据点的原始横坐标。
|
||
* @param [IN] rawY 数据点的原始纵坐标。
|
||
* @param [IN] pinX 横向贴边位置(光标在视界外时的钉住横坐标)。
|
||
* @param [IN] clipTop 网格可见区上边界。
|
||
* @param [IN] clipBottom 网格可见区下边界。
|
||
* @param [IN] topReserve 上边界额外需要预留的高度(例如时间光标的顶部三角把手,
|
||
* 避免数据点三角压住光标把手)。默认 0。
|
||
* @return 标记几何。
|
||
*/
|
||
DataMarker MakeDataMarker(float rawX, float rawY, float pinX, float clipTop,
|
||
float clipBottom, float topReserve = 0.0f) const;
|
||
|
||
/**
|
||
* @brief 绘制数据点标记:视界内为圆点,越界时在对应边缘绘制指示方向的三角。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] marker 标记几何。
|
||
* @param [IN] radius 圆点半径。
|
||
* @param [IN] fillColor 前景色(圆点/三角内部)。
|
||
* @param [IN] ringColor 描边色(圆环/三角外框)。
|
||
*/
|
||
void PaintDataMarker(gd::Graphics& g, const DataMarker& marker, float radius,
|
||
std::uint32_t fillColor, std::uint32_t ringColor) const;
|
||
|
||
/**
|
||
* @brief 放置一个光标取值胶囊:先夹到网格可见范围内,再向下(必要时向上)避让
|
||
* 已占用的标注矩形,最后登记到 `m_cursorLabelRects`。
|
||
* @param [IN,OUT] rect 胶囊矩形(Y 会被调整)。
|
||
* @param [IN] preferredTop 期望的顶边纵坐标(避让前)。
|
||
* @param [IN] textHeight 胶囊高度。
|
||
* @param [IN] clipTop 网格可见区上边界。
|
||
* @param [IN] clipBottom 网格可见区下边界。
|
||
*/
|
||
void PlaceCursorLabel(gd::RectF& rect, float preferredTop, float textHeight, float clipTop,
|
||
float clipBottom);
|
||
|
||
/**
|
||
* @brief 统计某信号穿越横向光标幅值的上/下边沿数量,结果带缓存。
|
||
* @param [IN] file 信号所属文件。
|
||
* @param [IN] signal 目标信号。
|
||
* @param [IN] edgeValue 横向光标所在的幅值。
|
||
* @param [IN] hasRange 是否只统计某个时间区间(双光标模式下为光标区间)。
|
||
* @param [IN] rangeMin 区间左端点时间。
|
||
* @param [IN] rangeMax 区间右端点时间。
|
||
* @param [OUT] rising 上升沿数量(从下往上穿越)。
|
||
* @param [OUT] falling 下降沿数量(从上往下穿越)。
|
||
*/
|
||
void EdgeCountsFor(const DataFile& file, const SignalData& signal, double edgeValue,
|
||
bool hasRange, double rangeMin, double rangeMax, std::size_t& rising,
|
||
std::size_t& falling) const;
|
||
|
||
/**
|
||
* @brief 计算实验性功能面板所需的宽度(排版与绘制共用,保证文字完整不被省略)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] kind 面板种类。
|
||
* @return 面板宽度(像素)。
|
||
*/
|
||
float EdgePanelWidth(int laneIndex, EdgePanelKind kind) const;
|
||
|
||
/**
|
||
* @brief 判断边沿统计是否限定在双光标区间内(双光标模式)。
|
||
* @return 限定在光标区间内返回 true。
|
||
*/
|
||
bool EdgeRangeActive() const;
|
||
|
||
/**
|
||
* @brief 边沿统计时间区间的左端点(取两条光标的较小时间)。
|
||
* @return 区间左端点。
|
||
*/
|
||
double EdgeRangeMin() const;
|
||
|
||
/**
|
||
* @brief 边沿统计时间区间的右端点(取两条光标的较大时间)。
|
||
* @return 区间右端点。
|
||
*/
|
||
double EdgeRangeMax() const;
|
||
|
||
/**
|
||
* @brief 双光标模式下面板标题的尾缀文本(“(光标区间内)”)。
|
||
* @return 尾缀文本,非双光标模式返回空串。
|
||
*/
|
||
std::wstring EdgeRangeSuffix() const;
|
||
|
||
/**
|
||
* @brief 绘制拖动中的缩放选区。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintRubberBand(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制内联数值输入框(时间光标或网格 Y 轴上下限)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintEditField(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制信号悬停提示与拖拽虚影。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintSignalInteraction(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 在绘图区底部绘制临时提示条(如“已放弃当前选区”)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintHint(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制网格纵向滚动条。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintScrollBar(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制网格标题(信号图例)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @param [IN] visibleTop 网格在绘图区内的可见上边界。
|
||
* @param [IN] visibleBottom 网格在绘图区内的可见下边界。
|
||
*/
|
||
void PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,
|
||
float visibleBottom);
|
||
|
||
/**
|
||
* @brief 判断给定矩形是否完整落在可见纵向区间内。
|
||
* @param [IN] rect 待测矩形。
|
||
* @param [IN] visibleTop 可见区上边界。
|
||
* @param [IN] visibleBottom 可见区下边界。
|
||
* @return 完整可见返回 true。
|
||
* @note GDI 文本绘制不会遵循 GDI+ 的裁剪区,必须显式判定,否则滚动时文字会溢出到工具栏。
|
||
*/
|
||
static bool IsFullyVisible(const gd::RectF& rect, float visibleTop, float visibleBottom);
|
||
|
||
/**
|
||
* @brief 绘制空状态提示文本。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintEmptyState(gd::Graphics& g);
|
||
|
||
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
||
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
||
const DataSet* m_dataSet = nullptr; /**< 数据集合(只读访问)。 */
|
||
DataSet* m_dataSetWritable = nullptr; /**< 数据集合(选中状态写入)。 */
|
||
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
|
||
TabConfig m_localTab; /**< 未绑定标签页时使用的占位配置。 */
|
||
HWND m_host = nullptr; /**< 剪贴板所有者窗口句柄。 */
|
||
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
||
|
||
gd::RectF m_area; /**< 绘图区整体区域。 */
|
||
gd::RectF m_plotRect; /**< 波形绘制区域。 */
|
||
gd::RectF m_timeAxisRect; /**< 时间轴区域。 */
|
||
gd::RectF m_scrollBarRect; /**< 网格滚动条区域。 */
|
||
|
||
float m_labelBandWidth = 66.0f; /**< 网格内侧纵轴标尺带宽度(逻辑像素)。 */
|
||
float m_timeAxisHeight = 32.0f; /**< 时间轴高度(逻辑像素)。 */
|
||
float m_minLaneHeight = 54.0f; /**< 网格最小高度(逻辑像素)。 */
|
||
float m_laneContentHeight = 0.0f; /**< 网格内容总高度。 */
|
||
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
|
||
|
||
PlotDragMode m_dragMode = PlotDragMode::NONE; /**< 当前拖拽模式。 */
|
||
PlotRubberAxis m_rubberAxis = PlotRubberAxis::UNDECIDED; /**< 选区放大拖拽的轴向。 */
|
||
int m_dragLane = -1; /**< 拖拽所作用的网格下标。 */
|
||
bool m_dragMoved = false; /**< 拖拽过程中是否发生了位移。 */
|
||
int m_dragStartX = 0; /**< 拖拽起点横坐标。 */
|
||
int m_dragStartY = 0; /**< 拖拽起点纵坐标。 */
|
||
int m_dragLastX = 0; /**< 上一次拖拽横坐标。 */
|
||
int m_dragLastY = 0; /**< 上一次拖拽纵坐标。 */
|
||
gd::RectF m_rubberRect; /**< 拖拽选区矩形。 */
|
||
|
||
SignalRef m_hoverSignal; /**< 鼠标悬停命中的信号(曲线高亮)。 */
|
||
SignalRef m_hoverLegend; /**< 鼠标悬停命中的图例条目(半高亮提示,不触发曲线拖拽提示)。 */
|
||
SignalRef m_dragSignal; /**< 正在被拖拽的信号。 */
|
||
int m_dragTargetLane = -1; /**< 拖拽信号时的目标网格下标。 */
|
||
int m_dragMouseX = 0; /**< 拖拽过程中鼠标的当前横坐标(虚影跟随)。 */
|
||
int m_dragMouseY = 0; /**< 拖拽过程中鼠标的当前纵坐标(虚影跟随)。 */
|
||
bool m_cursorHover = false; /**< 鼠标是否悬停在某条时间光标上。 */
|
||
int m_hoverCursor = 0; /**< 悬停/拖拽中的光标序号(1 或 2,0 表示无)。 */
|
||
int m_activeCursor = 1; /**< 最近操作过的光标:内容重叠时它绘制在上层。 */
|
||
int m_editCursor = 1; /**< 内联输入所作用的 Time 光标序号。 */
|
||
int m_spanDragStartX = 0; /**< 差值标签拖拽起点横坐标。 */
|
||
double m_spanDragTime1 = 0.0; /**< 差值标签拖拽起始时两条光标的时间。 */
|
||
double m_spanDragTime2 = 0.0;
|
||
int m_cursorGrabOffset = 0; /**< 从光标时间标签上抓起时的横向偏移(像素)。 */
|
||
PlotEditTarget m_editTarget = PlotEditTarget::NONE; /**< 当前内联输入目标。 */
|
||
TextEdit m_edit; /**< 输入框编辑模型(光标/选区/剪贴板)。 */
|
||
int m_editLane = -1; /**< 内联输入所作用的网格下标。 */
|
||
SignalRef m_editMeanRef; /**< 均值滤波参数编辑所作用的信号。 */
|
||
/** 网格拖拽重排状态(拖动图例中的 GX 徽标)。 */
|
||
int m_laneDragFrom = -1; /**< 被拖拽的网格下标,-1 表示未拖拽。 */
|
||
int m_laneDragTarget = -1; /**< 当前插入位置(0..网格数)。 */
|
||
int m_laneDragMouseY = 0; /**< 最近一次鼠标纵坐标(被拖网格自由跟随)。 */
|
||
float m_laneDragGrabOffset = 0.0f; /**< 抓取时鼠标相对网格顶边的偏移。 */
|
||
SignalRef m_editSignal; /**< 内联输入所作用的信号(图例改名)。 */
|
||
gd::RectF m_editFieldRect; /**< 本次编辑输入框的矩形(用于点外提交判定)。 */
|
||
SignalRef m_pendingRenameRef; /**< 待宿主窗口应用的信号重命名引用。 */
|
||
std::wstring m_pendingRenameName; /**< 待宿主窗口应用的信号新名称。 */
|
||
bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */
|
||
|
||
int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */
|
||
int m_pendingWeakBoostLane = -1; /**< 待构建的弱波增强信号所属网格下标(-1 表示无请求)。 */
|
||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||
std::vector<WeakBoostBuild> m_pendingWeakBoostItems; /**< 待构建的弱波增强信号及其参数。 */
|
||
int m_boostDragLane = -1; /**< 正在拖动滑块的面板所属网格。 */
|
||
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||
|
||
/**
|
||
* @brief 实验性功能浮动面板的 ✕ 处于"已按下、等待抬起确认"状态。
|
||
*
|
||
* 关闭类 ✕ 统一改为左键**抬起**时生效:按下只记录目标,抬起时指针仍在该 ✕ 上才真正
|
||
* 关闭对应功能;按住后移开再松开视为放弃。
|
||
*/
|
||
bool m_pendingPanelClose = false; /**< 是否有待确认的面板关闭请求。 */
|
||
int m_pendingPanelCloseLane = -1; /**< 待关闭面板所属网格下标。 */
|
||
EdgePanelKind m_pendingPanelCloseKind = EdgePanelKind::COUNT; /**< 待关闭面板的功能类型。 */
|
||
|
||
int m_hoverLane = -1; /**< 当前悬停网格下标。 */
|
||
int m_mouseX = 0; /**< 最近一次鼠标横坐标(用于悬停提示定位)。 */
|
||
int m_mouseY = 0; /**< 最近一次鼠标纵坐标(用于悬停提示定位)。 */
|
||
bool m_spanHover = false; /**< 鼠标是否悬停在双光标差值标签上。 */
|
||
std::wstring m_hintText; /**< 临时提示文本(空表示不显示)。 */
|
||
std::uint64_t m_hintShownAt = 0; /**< 提示出现的时刻(毫秒,GetTickCount64)。 */
|
||
bool m_hintSticky = false; /**< 提示是否处于粘滞状态(拖动期间维持显示,不淡出)。 */
|
||
gd::RectF m_cursorSpanRect; /**< 最近一次绘制时差值标签的矩形(用于命中拖拽/双击)。 */
|
||
std::vector<gd::RectF> m_cursorLabelRects; /**< 本次绘制已占用的取值标注矩形(用于避让重叠)。 */
|
||
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
|
||
|
||
/**
|
||
* @brief 边沿统计缓存项。
|
||
*
|
||
* 计数只由「采样下标区间 [begin, end) 与判定基准(横向光标幅值)」决定,
|
||
* 因此**时间区间不进缓存键**:拖动时间光标时区间每帧都在变,若按时间区间做键就会
|
||
* 每帧全量重扫整段区间(亿级采样下正是拖拽卡顿的主因,实测单帧数十毫秒)。
|
||
* 改为记住「上一帧统计过的下标区间」:新区间与旧区间共享一个端点时(拖动时必然如此)
|
||
* 只扫描两端差集并增量加减,单帧代价与光标位移成正比、与区间长度无关。
|
||
*/
|
||
struct EdgeCountEntry
|
||
{
|
||
const DataFile* file = nullptr; /**< 所属数据分组。 */
|
||
const SignalData* signal = nullptr; /**< 目标信号。 */
|
||
double edgeValue = 0.0; /**< 判定阈值(横向光标幅值)。 */
|
||
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
|
||
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
|
||
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
|
||
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
|
||
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
|
||
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
|
||
};
|
||
mutable std::vector<EdgeCountEntry> m_edgeCountCache; /**< 边沿统计缓存(纯缓存,逻辑上不影响对象状态)。 */
|
||
|
||
/** 多次迭代均值滤波曲线缓存项(按「信号指针 + 均值点数 + 迭代次数」标识)。 */
|
||
struct MeanCurveEntry
|
||
{
|
||
const SignalData* signal = nullptr;
|
||
int points = 5;
|
||
int iterations = 1;
|
||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||
};
|
||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||
|
||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */
|
||
struct WeakBoostCurveEntry
|
||
{
|
||
const SignalData* signal = nullptr;
|
||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||
};
|
||
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
||
|
||
/** 中心轴边沿统计缓存项(增量统计机制与 `EdgeCountEntry` 完全一致)。 */
|
||
struct EdgeCenterCountEntry
|
||
{
|
||
const DataFile* file = nullptr; /**< 所属数据分组。 */
|
||
const SignalData* signal = nullptr; /**< 目标信号。 */
|
||
int points = 5; /**< 均值点数 X。 */
|
||
int iterations = 1; /**< 迭代次数 N。 */
|
||
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
|
||
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
|
||
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
|
||
std::size_t end = 0; /**< 已统计的采样区间终点(不含)。 */
|
||
std::size_t rising = 0; /**< 该区间内的上升沿数量。 */
|
||
std::size_t falling = 0; /**< 该区间内的下降沿数量。 */
|
||
};
|
||
mutable std::vector<EdgeCenterCountEntry> m_edgeCenterCountCache; /**< 中心轴边沿统计缓存。 */
|
||
|
||
// 「边沿计数」横向光标 / 面板的拖拽状态。
|
||
int m_edgeDragLane = -1; /**< 正在拖动的横向光标所属网格。 */
|
||
int m_edgePanelDragLane = -1; /**< 正在拖动的统计面板所属网格。 */
|
||
EdgePanelKind m_edgePanelDragKind = EdgePanelKind::COUNT; /**< 正在拖动的面板种类。 */
|
||
gd::PointF m_edgePanelDragOffset; /**< 抓取面板时的相对偏移(保持不跳变)。 */
|
||
|
||
/**
|
||
* @brief 图例条目的命中信息(用于点选与双击改名)。
|
||
*/
|
||
struct LegendHit
|
||
{
|
||
SignalRef ref; /**< 对应信号。 */
|
||
gd::RectF rect; /**< 条目矩形(已含色块与文本)。 */
|
||
};
|
||
|
||
std::vector<LegendHit> m_legendHits; /**< 最近一次绘制时记录的图例条目矩形。 */
|
||
|
||
/**
|
||
* @brief 折线顶点缓存条目。
|
||
*
|
||
* 折线抽稀是 O(采样点数) 的(百万级采样约 2~3ms/条),
|
||
* 而绘制、悬停命中检测每帧都要用到同一批顶点。
|
||
* 用“构建折线时读取的全部输入”作为键缓存顶点,
|
||
* 视图/网格几何未变化时可直接复用,避免重复扫描全量采样。
|
||
*/
|
||
struct PolylineCacheEntry
|
||
{
|
||
SignalRef ref; /**< 信号引用。 */
|
||
const DataFile* file = nullptr; /**< 所属文件(内容不可变)。 */
|
||
std::size_t sampleCount = 0; /**< 采样点数(数据变化时改变)。 */
|
||
double viewMin = 0.0; /**< 时间轴显示下界。 */
|
||
double viewMax = 0.0; /**< 时间轴显示上界。 */
|
||
float plotX = 0.0f; /**< 绘图区左边界。 */
|
||
float plotWidth = 0.0f; /**< 绘图区宽度。 */
|
||
float laneY = 0.0f; /**< 网格上边界。 */
|
||
float laneHeight = 0.0f; /**< 网格高度。 */
|
||
double laneYMin = 0.0; /**< 网格幅值下界。 */
|
||
double laneYMax = 0.0; /**< 网格幅值上界。 */
|
||
std::vector<gd::PointF> points; /**< 折线顶点。 */
|
||
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
|
||
|
||
/**
|
||
* @brief 是否处于「稠密包络」模式(采样点密度超过像素分辨率)。
|
||
*
|
||
* 此时 `points` 是每列 min/max 交替的锯齿,逐段描边的光栅化开销极大
|
||
* (实测每网格约 14ms);改用下面的「包络带」一次填充后约 1.7ms,
|
||
* 且保留抗锯齿,静态与拖拽帧的观感完全一致。
|
||
*/
|
||
bool bandMode = false;
|
||
|
||
/** 包络带顶点:每段为「上包络正向 + 下包络反向」,闭合后即为该段的填充多边形。 */
|
||
std::vector<gd::PointF> band;
|
||
|
||
/** 每段包络带在 `band` 中的起始下标。 */
|
||
std::vector<std::size_t> bandBreaks;
|
||
};
|
||
|
||
/**
|
||
* @brief 取得(必要时重建)指定信号在当前几何下的折线顶点。
|
||
* @param [IN] ref 信号引用(缓存键的一部分)。
|
||
* @param [IN] resolved 已解析的信号。
|
||
* @param [IN] lane 网格绘制信息。
|
||
* @return 缓存条目指针,无法构建时返回 nullptr。
|
||
*/
|
||
const PolylineCacheEntry* AcquirePolyline(const SignalRef& ref, const ResolvedSignal& resolved,
|
||
const LaneVisual& lane) const;
|
||
|
||
mutable std::deque<PolylineCacheEntry> m_polylineCache; /**< 折线顶点缓存(按几何键复用)。 */
|
||
|
||
/**
|
||
* @brief 静态场景缓存的几何/内容键。
|
||
*
|
||
* 只要这组参数完全一致,"背景 + 全部网格 + 时间轴"的渲染结果就逐像素相同,
|
||
* 可以直接复用离屏位图,无需重算百万级采样的包络与绘制。
|
||
* 这使鼠标悬停、拖拽选区等"只改一小块叠加层"的交互不再重绘波形:
|
||
* 大文件下整幅重绘约 80ms,命中缓存时只需 1~2ms 贴图。
|
||
*/
|
||
struct SceneCacheKey
|
||
{
|
||
double viewMin = 0.0; /**< 时间轴下界。 */
|
||
double viewMax = 0.0; /**< 时间轴上界。 */
|
||
float laneScroll = 0.0f; /**< 网格滚动偏移。 */
|
||
int laneCount = 0; /**< 网格数量。 */
|
||
float areaX = 0.0f; /**< 绘图区左上角。 */
|
||
float areaY = 0.0f;
|
||
float areaW = 0.0f; /**< 绘图区尺寸。 */
|
||
float areaH = 0.0f;
|
||
float scale = 1.0f; /**< DPI 缩放。 */
|
||
int haloFile = -1; /**< 已绘入缓存的拖拽光晕信号(无则 -1)。 */
|
||
int haloSignal = -1;
|
||
std::uint64_t contentHash = 0; /**< 信号集合/显示样式/网格幅值等内容指纹。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 构造当前场景的缓存键。
|
||
* @return 缓存键。
|
||
*/
|
||
SceneCacheKey MakeSceneCacheKey() const;
|
||
|
||
/**
|
||
* @brief 判断当前缓存键是否与已捕获的场景一致。
|
||
* @return 可直接贴图返回 true。
|
||
*/
|
||
bool SceneCacheMatches() const;
|
||
|
||
/**
|
||
* @brief 计算场景内容指纹(信号集合、显示样式、网格幅值、图例文案)。
|
||
*
|
||
* 视图几何以外的任何内容变化(改颜色/线宽/透明度/样式、重命名、增删信号)
|
||
* 都会改变该指纹,从而让静态场景缓存自动失效,无需在各处手工调用失效接口。
|
||
*
|
||
* @param [IN] includeStyles 是否把逐信号外观样式(颜色/不透明度/线宽/模式)计入指纹。
|
||
* @return 指纹值。
|
||
*/
|
||
std::uint64_t SceneContentHash(bool includeStyles = true) const;
|
||
|
||
/**
|
||
* @brief 场景内容中**不含逐信号外观样式**的指纹。
|
||
*
|
||
* 只包含网格结构、视图几何、网格幅值、信号集合与可见性、图例文案、选中项等
|
||
* "变化后必须整幅重建"的内容;逐信号的 `color / opacity / lineWidth / mode`
|
||
* 一律不参与。
|
||
*
|
||
* 用途:外观样式变化时(典型场景是拖动自定义颜色的滑块)波形几何完全不变,
|
||
* 只要该指纹一致,就可以只重画"颜色变了的那个网格"再写回缓存,
|
||
* 而不必整幅重绘全部网格(VERY_BIG_DATA 实测整幅 30ms+、单网格约 4ms)。
|
||
*
|
||
* @return 指纹值。
|
||
*/
|
||
std::uint64_t SceneGeometryHash() const;
|
||
|
||
/**
|
||
* @brief 静态场景缓存中"网格内信号外观样式"的快照条目。
|
||
*/
|
||
struct SceneStyleEntry
|
||
{
|
||
std::uint32_t color = 0; /**< 信号色(未叠加不透明度)。 */
|
||
float opacity = 1.0f; /**< 不透明度。 */
|
||
float lineWidth = 1.0f; /**< 线宽。 */
|
||
int mode = 0; /**< 绘制模式。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 采集当前各网格内可见信号的外观样式快照。
|
||
* @param [OUT] out 采集结果(按网格分组)。
|
||
*/
|
||
void CollectSceneStyles(std::vector<std::vector<SceneStyleEntry>>& out) const;
|
||
|
||
/**
|
||
* @brief 判断当前相对缓存是否"只有外观样式发生了变化"。
|
||
*
|
||
* 成立条件:`SceneGeometryHash()` 与缓存一致(网格结构、视图几何、信号集合
|
||
* 与文字都没变),且至少有一个网格内信号的外观样式不同。
|
||
*
|
||
* @param [OUT] lanes 外观发生变化的网格下标(升序)。
|
||
* @return 可以走"局部重画"快路径返回 true。
|
||
*/
|
||
bool FindStyleOnlyLanes(std::vector<int>& lanes) const;
|
||
|
||
/**
|
||
* @brief 只重画指定网格并把结果写回静态场景缓存。
|
||
*
|
||
* 必须在 `BlitSceneCache()` 之后调用:贴图已经完成,这里把受影响网格的
|
||
* "底色 + 网格线 + 波形 + 标尺 + 图例"重画到画布上,再整块拷回缓存位图,
|
||
* 使缓存内容与当前样式一致(下一帧即可重新走"完全命中"快路径)。
|
||
*
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] lanes 需要重画的网格下标。
|
||
*/
|
||
void RepaintSceneCachePatch(gd::Graphics& g, const std::vector<int>& lanes);
|
||
|
||
/**
|
||
* @brief 当前应当绘入静态场景缓存的信号光晕(仅拖拽中的信号)。
|
||
*
|
||
* 拖拽信号的光晕在整个拖拽过程中不变,可以进入缓存;
|
||
* 悬停光晕随鼠标移动,必须画在缓存之外。
|
||
*
|
||
* @return 信号引用,无则返回无效引用。
|
||
*/
|
||
SignalRef SceneHaloRef() const;
|
||
|
||
/**
|
||
* @brief 绘制静态场景缓存之外的"悬停高亮"(悬停网格底色 + 悬停信号光晕)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintHoverHighlight(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制图例条目的悬停半高亮提示(在静态场景贴图之后绘制)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintLegendHighlight(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 绘制某条信号的悬停/拖拽光晕(按缓存的折线模式选择最省的画法)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] resolved 已解析的信号。
|
||
* @param [IN] cached 该信号的折线缓存项(包络带模式下会走带外扩填充)。
|
||
*/
|
||
void PaintSignalHaloFor(gd::Graphics& g, const ResolvedSignal& resolved,
|
||
const PolylineCacheEntry& cached);
|
||
|
||
/**
|
||
* @brief 绘制某条信号的悬停/拖拽光晕(折线描边版本)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] resolved 已解析的信号。
|
||
* @param [IN] points 折线顶点。
|
||
* @param [IN] breaks 每段折线的起始下标。
|
||
*/
|
||
void PaintSignalHalo(gd::Graphics& g, const ResolvedSignal& resolved,
|
||
const std::vector<gd::PointF>& points, const std::vector<std::size_t>& breaks);
|
||
|
||
/**
|
||
* @brief 稠密包络带的光晕:把带整体外扩 `extra` 像素后填充一次。
|
||
*
|
||
* 比用宽画笔描整条折线快一个数量级(含大量近垂直跳变的信号上尤其明显),
|
||
* 观感也更均匀。
|
||
*
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] band 包络带顶点(上包络正向 + 下包络反向)。
|
||
* @param [IN] bandBreaks 各段起点。
|
||
* @param [IN] extra 纵向额外外扩量(像素)。
|
||
*/
|
||
void PaintBandHalo(gd::Graphics& g, const std::vector<gd::PointF>& band,
|
||
const std::vector<std::size_t>& bandBreaks, float extra);
|
||
|
||
/**
|
||
* @brief 计算某网格图例中 GX 编号徽标的矩形(绘制与命中测试共用,保证一致)。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 徽标矩形;网格不可见时返回空矩形。
|
||
*/
|
||
gd::RectF LaneBadgeRect(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 查询坐标命中的网格编号徽标(用于发起网格次序拖拽)。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 命中的网格下标,未命中返回 -1。
|
||
*/
|
||
int LaneBadgeAtPoint(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 网格拖拽重排时某条网格的显示位移(像素)。
|
||
*
|
||
* 被拖拽的网格自由跟随鼠标;它路径上的其它网格整体上/下让位一格。
|
||
* @param [IN] laneIndex 网格下标。
|
||
* @return 纵向位移。
|
||
*/
|
||
float LaneDragOffset(int laneIndex) const;
|
||
|
||
/**
|
||
* @brief 网格拖拽重排:按鼠标位置计算插入位置(0..网格数)。
|
||
* @param [IN] mouseY 鼠标客户区纵坐标。
|
||
* @return 插入位置。
|
||
*/
|
||
int LaneDragTargetAt(int mouseY) const;
|
||
|
||
/**
|
||
* @brief 绘制网格拖拽重排过程中的内容(逐网格按位移贴图 + 目标位置高亮)。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
*/
|
||
void PaintLaneReorder(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 结束网格拖拽重排:若目标位置与原位置不同则真正重排并同步编号。
|
||
*/
|
||
void FinishLaneReorder();
|
||
|
||
/**
|
||
* @brief 确保离屏缓存位图存在且尺寸匹配。
|
||
* @param [IN] width 位图宽度(像素)。
|
||
* @param [IN] height 位图高度(像素)。
|
||
* @return 可用返回 true。
|
||
*/
|
||
bool EnsureSceneCache(int width, int height);
|
||
|
||
/**
|
||
* @brief 把当前画布内容(绘图区底色 + 全部网格 + 时间轴)拷贝进离屏缓存。
|
||
*
|
||
* 缓存区域严格限定为绘图区(m_area)。悬停与拖拽产生的内容一律不进入缓存,
|
||
* 因此在任何交互过程中都可以安全复用,只贴图而不再重绘波形。
|
||
*
|
||
* @param [IN,OUT] g 当前绘图上下文(必须未设置裁剪区)。
|
||
* @return 拷贝成功返回 true。
|
||
*/
|
||
bool CaptureSceneCache(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 把离屏缓存贴回画布。
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @return 命中缓存并贴图成功返回 true。
|
||
*/
|
||
bool BlitSceneCache(gd::Graphics& g);
|
||
|
||
/**
|
||
* @brief 丢弃缓存标记(下次绘制会重新捕获)。
|
||
*/
|
||
void InvalidateSceneCache();
|
||
|
||
/**
|
||
* @brief 释放离屏缓存占用的 GDI 对象。
|
||
*/
|
||
void ReleaseSceneCache();
|
||
|
||
HBITMAP m_sceneBitmap = nullptr; /**< 离屏缓存位图。 */
|
||
HDC m_sceneDc = nullptr; /**< 离屏缓存设备上下文。 */
|
||
HGDIOBJ m_sceneOldBitmap = nullptr; /**< 离屏 DC 原选位图。 */
|
||
int m_sceneWidth = 0; /**< 离屏位图宽度。 */
|
||
int m_sceneHeight = 0; /**< 离屏位图高度。 */
|
||
int m_sceneX = 0; /**< 缓存对应的客户区横坐标(绘图区左边界)。 */
|
||
int m_sceneY = 0; /**< 缓存对应的客户区纵坐标(绘图区上边界)。 */
|
||
bool m_sceneValid = false; /**< 缓存内容是否有效。 */
|
||
SceneCacheKey m_sceneKey; /**< 缓存对应的场景参数。 */
|
||
/** 缓存捕获时"不含逐信号外观样式"的内容指纹(用于识别仅样式变化)。 */
|
||
std::uint64_t m_sceneGeometryHash = 0;
|
||
/** 缓存捕获时各网格内信号的外观样式快照(按网格分组,与 m_sceneStyles 同序)。 */
|
||
std::vector<std::vector<SceneStyleEntry>> m_sceneStyles;
|
||
|
||
/**
|
||
* @brief 查询坐标命中的图例条目。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 命中的信号引用,未命中返回无效引用。
|
||
*/
|
||
SignalRef LegendAtPoint(int x, int y) const;
|
||
};
|
||
|
||
} // namespace jdi
|
||
|
||
#endif // __PLOT_VIEW_HPP__
|