#ifndef __SIGNAL_PANEL_HPP__ #define __SIGNAL_PANEL_HPP__ #include #include #include #include #include #include #include "DataModel.hpp" #include "PaintUtil.hpp" #include "Theme.hpp" namespace jdi { /** * @brief 左侧信号面板:按文件分组的信号树、可见性勾选与样式编辑。 * * 面板只读取数据层(DataSet),全部显示配置读写均落在当前标签页(TabConfig)上, * 因此不同标签页的信号勾选与样式互不影响。 */ class SignalPanel { public: SignalPanel(); /** * @brief 绑定宿主窗口、字体与主题资源。 * @param [IN] host 宿主窗口句柄,用于弹出上下文菜单。 * @param [IN] fonts 字体集合,需在面板生命周期内保持有效。 * @param [IN] theme 主题配色,需在面板生命周期内保持有效。 */ void Setup(HWND host, const UiFonts* fonts, const Theme* theme); /** * @brief 绑定数据层与当前标签页配置。 * @param [IN] dataSet 数据集合,允许为空。 * @param [IN] tab 当前标签页配置,允许为空。 */ void SetContext(const DataSet* dataSet, TabConfig* tab); /** * @brief 标记列表结构失效(数据或折叠状态变化后调用)。 */ void InvalidateRows() { m_rowsDirty = true; } /** * @brief 判断列表内容是否需要纵向滚动。 * @return 内容超出可视区域返回 true。 */ bool NeedsScrollBar() const { return m_contentHeight > m_listRect.Height + 0.5f; } /** * @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 OnDoubleClick(int x, int y); /** * @brief 处理鼠标右键按下(弹出上下文菜单)。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @return 需要重绘返回 true。 */ bool OnRButtonDown(int x, int y); /** * @brief 处理滚轮滚动(列表纵向滚动)。 * @param [IN] delta 滚轮增量。 * @return 需要重绘返回 true。 */ bool OnMouseWheel(int delta); /** * @brief 处理鼠标离开面板。 * @return 视觉状态发生变化返回 true。 */ bool OnMouseLeave(); /** * @brief 判断坐标是否落在面板区域内。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @return 落在区域内返回 true。 */ bool HitTest(int x, int y) const; /** * @brief 将列表滚动到指定信号所在位置。 * @param [IN] ref 目标信号引用。 */ void ScrollToSignal(const SignalRef& ref); private: /** * @brief 列表行的类型。 */ enum class RowKind : int { FILE = 0, /**< 文件分组标题行。 */ SIGNAL = 1 /**< 信号行。 */ }; /** * @brief 行的可交互区域。 */ enum class RowPart : int { NONE = 0, /**< 未命中。 */ DISCLOSURE, /**< 折叠三角。 */ CHECK, /**< 复选框。 */ BODY /**< 行主体。 */ }; /** * @brief 样式编辑区中的可点击条目类型。 */ enum class StyleHit : int { NONE = 0, /**< 未命中。 */ COLOR, /**< 颜色色块。 */ WIDTH, /**< 线宽按钮。 */ MODE, /**< 绘制方式按钮。 */ LANE, /**< 网格选择按钮。 */ TOGGLE_ALL /**< 全部显示/隐藏按钮。 */ }; /** * @brief 列表中的一行(文件分组或信号)。 */ struct ListRow { RowKind kind = RowKind::SIGNAL; /**< 行类型。 */ int fileIndex = -1; /**< 所属文件下标。 */ int signalIndex = -1; /**< 信号下标(仅信号行有效)。 */ float height = 0.0f; /**< 行高(像素)。 */ }; /** * @brief 重新构建列表行集合。 */ void BuildRows(); /** * @brief 在需要时重建列表行集合。 */ void EnsureRows(); /** * @brief 依据内容高度重算滚动条可见性与列表可用宽度。 */ void RefreshScrollBar(); /** * @brief 计算列表内容总高度。 * @return 内容高度(像素)。 */ float ContentHeight() const; /** * @brief 限制滚动偏移量到合法范围。 */ void ClampScroll(); /** * @brief 查询坐标命中的行下标与行内区域。 * @param [IN] x 客户区横坐标。 * @param [IN] y 客户区纵坐标。 * @param [OUT] part 行内区域类型。 * @return 行下标,未命中返回 -1。 */ int HitRow(int x, int y, RowPart& part); /** * @brief 计算行的纵向矩形。 * @param [IN] row 行下标。 * @return 行矩形。 */ gd::RectF RowRect(int row) const; /** * @brief 绘制文件分组标题行。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] row 行下标。 */ void PaintFileRow(gd::Graphics& g, int row); /** * @brief 绘制信号行。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] row 行下标。 */ void PaintSignalRow(gd::Graphics& g, int row); /** * @brief 绘制样式编辑区。 * @param [IN,OUT] g 绘图上下文。 */ void PaintStyleSection(gd::Graphics& g); /** * @brief 绘制列表滚动条。 * @param [IN,OUT] g 绘图上下文。 */ void PaintScrollBar(gd::Graphics& g); /** * @brief 计算滚动条滑块矩形。 * @return 滑块矩形,无需滚动时返回空矩形。 */ gd::RectF ScrollThumbRect() const; /** * @brief 弹出信号右键菜单。 * @param [IN] ref 目标信号引用。 * @param [IN] screenX 屏幕坐标横坐标。 * @param [IN] screenY 屏幕坐标纵坐标。 */ void ShowSignalMenu(const SignalRef& ref, int screenX, int screenY); /** * @brief 弹出文件分组右键菜单。 * @param [IN] fileIndex 目标文件下标。 * @param [IN] screenX 屏幕坐标横坐标。 * @param [IN] screenY 屏幕坐标纵坐标。 */ void ShowFileMenu(int fileIndex, int screenX, int screenY); /** * @brief 弹出网格分配菜单。 */ void ShowLaneMenu(); /** * @brief 判断当前标签页中全部信号是否均为可见状态。 * @return 全部可见且至少有一条信号返回 true。 */ bool AreAllSignalsVisible() const; /** * @brief 统计全部信号数量。 * @return 信号总数。 */ int TotalSignalCount() const; HWND m_host = nullptr; /**< 宿主窗口句柄。 */ const UiFonts* m_fonts = nullptr; /**< 字体资源。 */ const Theme* m_theme = nullptr; /**< 主题资源。 */ const DataSet* m_dataSet = nullptr; /**< 数据集合。 */ TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */ float m_scale = 1.0f; /**< DPI 缩放比例。 */ gd::RectF m_area; /**< 面板整体区域。 */ gd::RectF m_headerRect; /**< 标题区域。 */ gd::RectF m_listRect; /**< 列表区域。 */ gd::RectF m_styleRect; /**< 样式编辑区域。 */ gd::RectF m_toggleAllRect; /**< 全部显示/隐藏按钮。 */ gd::RectF m_laneButtonRect; /**< 网格选择按钮。 */ gd::RectF m_scrollBarRect; /**< 滚动条区域。 */ std::array m_colorRects; /**< 颜色色块矩形。 */ std::array m_widthRects; /**< 线宽按钮矩形。 */ std::array m_modeRects; /**< 绘制方式按钮矩形。 */ int m_colorCount = 0; /**< 实际可用的颜色数量。 */ std::vector m_rows; /**< 列表行集合(惰性缓存)。 */ bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */ float m_contentHeight = 0.0f; /**< 列表内容总高度。 */ float m_listFullWidth = 0.0f; /**< 列表区不含滚动条的完整宽度。 */ bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */ float m_scrollOffset = 0.0f; /**< 列表滚动偏移量。 */ bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */ int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */ int m_hoverRow = -1; /**< 当前悬停行。 */ RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */ StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */ int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */ }; } // namespace jdi #endif // __SIGNAL_PANEL_HPP__