#ifndef __PLOT_VIEW_HPP__ #define __PLOT_VIEW_HPP__ #include #include #include #include #include #include "DataModel.hpp" #include "PaintUtil.hpp" #include "Theme.hpp" namespace jdi { /** * @brief 绘图区拖拽交互模式。 */ enum class PlotDragMode : int { NONE = 0, /**< 未拖拽。 */ PAN_TIME, /**< 水平平移时间轴(常规模式下的左键拖拽)。 */ RUBBER_TIME, /**< 时间轴缩放选区(竖向贯穿全高)。 */ RUBBER_VALUE, /**< Y 轴缩放选区(限定在某个网格内)。 */ SCROLL_LANES, /**< 拖动网格滚动条。 */ CURSOR_MOVE /**< 拖拽时间光标。 */ }; /** * @brief 波形绘图区:网格、时间轴、光标、缩放与自适应。 * * 绘图区同时依赖数据层(DataSet,只读)与当前标签页配置(TabConfig,可写), * 视图范围、网格滚动与时间光标均保存在标签页配置中,因此各标签页互不干扰。 */ class PlotView { public: PlotView(); /** * @brief 绑定字体与主题资源。 * @param [IN] fonts 字体集合,需在视图生命周期内保持有效。 * @param [IN] theme 主题配色,需在视图生命周期内保持有效。 */ void Setup(const UiFonts* fonts, const Theme* theme); /** * @brief 绑定数据层与当前标签页配置。 * @param [IN] dataSet 数据集合,允许为空。 * @param [IN] tab 当前标签页配置,允许为空。 */ void SetContext(const 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 客户区纵坐标。 * @param [IN] delta 滚轮增量。 * @param [IN] shift 是否按下 Shift(水平平移时间轴)。 * @return 需要重绘返回 true。 */ bool OnMouseWheel(int x, int y, 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] 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 获取网格当前内容总高度。 * @return 内容高度(像素)。 */ float LaneContentHeight() const { return m_laneContentHeight; } /** * @brief 清除已启用的时间光标。 */ void DisableCursor(); /** * @brief 切换时间光标:启用时置于当前视图中心,再调用则关闭。 * @return 状态发生变化返回 true。 */ bool ToggleCursor(); /** * @brief 判断时间光标是否已启用。 * @return 启用返回 true。 */ bool CursorEnabled() const; /** * @brief 获取当前光标对应的时间。 * @return 光标时间。 */ double CursorTime() const; /** * @brief 切换拖拽缩放模式(再次调用同一模式则取消)。 * @param [IN] mode 目标模式。 * @return 状态发生变化返回 true。 */ bool ToggleZoomMode(PlotZoomMode mode); /** * @brief 获取当前拖拽缩放模式。 * @return 模式枚举。 */ PlotZoomMode CurrentZoomMode() const; /** * @brief 判断是否已进入精确时间输入状态。 * @return 处于编辑态返回 true。 */ bool IsEditingCursor() const { return m_cursorEditing; } /** * @brief 处理编辑态下的字符输入。 * @param [IN] character 输入字符。 * @return 界面状态发生变化返回 true。 */ bool OnEditChar(wchar_t character); /** * @brief 处理编辑态下的按键。 * @param [IN] key 虚拟键码。 * @return 界面状态发生变化返回 true。 */ bool OnEditKey(WPARAM key); /** * @brief 获取当前视图左边界时间。 * @return 视图左边界时间。 */ double ViewMinTime() const; /** * @brief 获取当前视图右边界时间。 * @return 视图右边界时间。 */ double ViewMaxTime() const; /** * @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; /**< 所属文件。 */ }; /** * @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 重新计算各网格的布局与幅值范围。 */ 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 计算时间光标当前的横坐标。 * @return 横坐标。 */ float CursorX() const; /** * @brief 计算时间读数标签(或编辑框)的矩形。 * @return 标签矩形,位于绘图区顶部以避免遮挡时间轴刻度。 */ gd::RectF CursorLabelRect() const; /** * @brief 判断给定横坐标是否落在时间光标附近(允许拖拽的容差范围)。 * @param [IN] x 横坐标。 * @return 命中返回 true。 */ bool IsOnCursorLine(int x) const; /** * @brief 进入时间光标的精确时间输入状态。 * @return 进入成功返回 true。 */ bool BeginCursorEdit(); /** * @brief 结束时间光标的精确时间输入。 * @param [IN] commit 是否提交当前输入。 * @return 状态发生变化返回 true。 */ bool EndCursorEdit(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& points, std::vector& breaks) const; /** * @brief 绘制单个网格。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] laneIndex 网格下标。 * @param [IN] lane 网格绘制信息。 * @param [IN] timeTicks 时间刻度序列。 * @param [IN] timeStep 时间刻度步长。 */ void PaintLane(gd::Graphics& g, int laneIndex, const LaneVisual& lane, const std::vector& timeTicks, double timeStep); /** * @brief 绘制底部时间轴。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] timeTicks 时间刻度序列。 * @param [IN] timeStep 时间刻度步长。 */ void PaintTimeAxis(gd::Graphics& g, const std::vector& timeTicks, double timeStep); /** * @brief 绘制时间光标、选区及其读数。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] lanes 网格绘制信息集合。 */ void PaintCursor(gd::Graphics& g, const std::vector& lanes); /** * @brief 绘制拖动中的缩放选区。 * @param [IN,OUT] g 绘图上下文。 */ void PaintRubberBand(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,OUT] g 绘图上下文。 * @param [IN] laneIndex 网格下标。 * @param [IN] lane 网格绘制信息。 * @param [IN] visibleTop 网格在绘图区内的可见上边界。 * @param [IN] visibleBottom 网格在绘图区内的可见下边界。 */ void PaintLaneReadout(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; /**< 数据集合。 */ TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */ TabConfig m_localTab; /**< 未绑定标签页时使用的占位配置。 */ float m_scale = 1.0f; /**< DPI 缩放比例。 */ gd::RectF m_area; /**< 绘图区整体区域。 */ gd::RectF m_plotRect; /**< 波形绘制区域。 */ gd::RectF m_timeAxisRect; /**< 时间轴区域。 */ gd::RectF m_scrollBarRect; /**< 网格滚动条区域。 */ float m_yAxisWidth = 74.0f; /**< 纵轴标签列宽度(逻辑像素)。 */ float m_timeAxisHeight = 32.0f; /**< 时间轴高度(逻辑像素)。 */ float m_laneGap = 6.0f; /**< 网格间距(逻辑像素)。 */ float m_minLaneHeight = 46.0f; /**< 网格最小高度(逻辑像素)。 */ float m_laneContentHeight = 0.0f; /**< 网格内容总高度。 */ bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */ PlotDragMode m_dragMode = PlotDragMode::NONE; /**< 当前拖拽模式。 */ 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; /**< 拖拽选区矩形。 */ bool m_cursorHover = false; /**< 鼠标是否悬停在时间光标上。 */ bool m_cursorEditing = false; /**< 是否处于精确时间输入状态。 */ bool m_cursorEditFresh = false; /**< 编辑框是否处于“首次输入整体替换”状态。 */ std::wstring m_cursorEditText; /**< 编辑框当前文本。 */ gd::RectF m_cursorEditRect; /**< 编辑框矩形(用于命中测试与绘制)。 */ int m_hoverLane = -1; /**< 当前悬停网格下标。 */ std::vector m_lanes; /**< 当前网格绘制信息集合。 */ }; } // namespace jdi #endif // __PLOT_VIEW_HPP__