一、全局「Y 轴自适应」跟随网格结构
* 新勾选信号产生的新网格现在会**继承**该开关:RebuildLanes 重建前先算
previousAllAutoFit(非空且每个网格都开着 = 全局开启的正式定义),LaneCarry 加
matched 标记,新出现的信号用它作为 fitYToSelection 的默认值。
* 新增 PlotView::ReapplyGlobalAutoFitIfEnabled(),在 Layout()(网格数量变化 →
Relayout)、RefreshLanes()(信号数量变化)、SplitLane() 之后立即重算一次。
⇒ 新勾选(按当前视界自适应)、多信号网格内取消显示某信号(剩余信号重新收敛)、
拆分网格(每份各自自适应)三项都覆盖。
二、信号列表「组名悬浮」
* 分组行被滚出列表上边界、而该组仍有信号留在列表里时,把分组行钉在列表顶部
(不透明底 + 底部边界线);整组翻过去后本组组名消失,改由下一组悬浮。
* 新增 SignalPanel::StickyGroupRowIndex() / PaintStickyGroupRow();RowRect 增加
m_rowYOverrideActive/m_rowYOverrideY 覆盖行顶部坐标(RowRect 是行内名称框 / ✕ /
徽标的唯一几何来源)。常规行循环跳过被钉住的那一行,避免同一行被画两次。
三、「少数数据点」信号的默认样式
* 有效采样点 1 个 → 「点线」+ 最大线宽(10px);2 … 16 个 → 「点线」+ 2px;
其余保持「线段」+ 1px。只在新建信号时生效(BuildSignalGroup 与
InsertDerivedSignal),不覆盖用户设置。
* 线宽范围常量统一到 DataModel.hpp(kSignalLineWidthMin/Max),SignalPanel 的线宽
滑块改为引用它们(原本两处各自硬编码,容易漂移)。
四、单点信号的光标吸附(吸附而非卡住)
* 旧逻辑要求时间轴至少 2 个采样点才能算「采样间距」,单点信号因此永远不吸附。
* 新增 SignalData::singlePointTime(加载时与 ComputeStats 同趟扫描缓存该点时刻),
SnapTimeToSample 新增半径分支(kCursorSnapPointRadiusPixels = 8 逻辑像素):
半径内吸附到该点,半径外自由移动(移开即自由,不是把光标吸住)。
五、放大到「采样点稀疏」时自动启用点线
* SignalStyle 新增运行态三字段:autoScatterActive / autoScatterSavedMode /
autoScatterUserOverride。
* AxisSparseInView()(采样点间距 > 半个绘图区宽度,与光标吸附上门限同源;单点时间轴
不算稀疏)+ UpdateAutoScatterStyles():进入阈值保存原绘制方式并切「点线」,
退出阈值(如适应视图)恢复;平移不改变缩放等级 ⇒ 结果稳定,不会样式抖动。
* 自动启用期间用户手动改了绘制方式则退出时**不恢复**(尊重用户选择);该判定必须在
恢复判断之前完成 —— 用户改完样式可能紧接着就退出阈值,否则会被覆盖。
* 钩子:SetTimeView(视界唯一写入口)与 Layout / RefreshLanes / SplitLane。
六、验证(离屏 .preflight/check:SW_HIDE 窗口、不注入真实鼠标键盘、进程/线程 BELOW_NORMAL)
65/65 通过(连跑 3 次稳定):[A1]/[A2]/[A3] 全局自适应、[B] 组名悬浮(含钉住行与自然
位置逐像素一致、整组翻过后组名消失)、[C] 稀疏默认样式边界(1/2/5/16/17/1000 点)、
[D] 单点吸附(半径内吸附、半径外自由、无残留粘滞)、[E] 自动点线(进入/退出/平移稳定/
用户手动改过后不覆盖/再次进入再启用)。
A/B:临时禁用六个机制后重跑 ⇒ 21 项如实失败(覆盖全部六项),证明断言有判别力。
make release 零警告(用户正运行 Target.exe,链接用临时 -o 名字验证通过)。
七、规则同步
Documents/agent_mem.md 新增 §9.29、§11.2 第 55–58 条硬约束与变更日志。
770 lines
27 KiB
C++
770 lines
27 KiB
C++
#ifndef __SIGNAL_PANEL_HPP__
|
||
#define __SIGNAL_PANEL_HPP__
|
||
|
||
#include <windows.h>
|
||
#include <objidl.h>
|
||
#include <gdiplus.h>
|
||
|
||
#include <array>
|
||
#include <cstdint>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "ColorPickerPopup.hpp"
|
||
#include "DataModel.hpp"
|
||
#include "PaintUtil.hpp"
|
||
#include "PopupMenu.hpp"
|
||
#include "Theme.hpp"
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
/**
|
||
* @brief 信号面板弹出菜单的命令标识。
|
||
*/
|
||
enum MenuCommand : int
|
||
{
|
||
CMD_SIGNAL_TOGGLE = 1, /**< 切换该信号可见性。 */
|
||
CMD_SIGNAL_SOLO = 2, /**< 仅显示该信号。 */
|
||
CMD_SIGNAL_SHOW_ALL = 3, /**< 显示全部信号。 */
|
||
CMD_SIGNAL_HIDE_ALL = 4, /**< 隐藏全部信号。 */
|
||
CMD_GROUP_SHOW_ALL = 10, /**< 选中该文件全部信号。 */
|
||
CMD_GROUP_HIDE_ALL = 11, /**< 取消该文件全部信号。 */
|
||
CMD_GROUP_REMOVE = 12, /**< 从工作区移除该文件。 */
|
||
CMD_COLOR_BASE = 100, /**< 预设颜色基准(配合色块下标)。 */
|
||
CMD_MODE_BASE = 200, /**< 绘制样式基准(配合下标)。 */
|
||
CMD_LANE_BASE = 300, /**< 网格归属基准(配合下标)。 */
|
||
CMD_LANE_NEW = 400, /**< 新建网格。 */
|
||
CMD_GAIN_EDIT = 500, /**< 「系数」二级面板里的系数输入框(幂等提交)。 */
|
||
CMD_GAIN_RESET = 501, /**< 「系数调整」标题栏右侧的还原按钮:把系数重置为 1。 */
|
||
CMD_OFFSET_RESET = 502, /**< 「偏移调整」标题栏右侧的还原按钮:两个偏移都恢复为默认值。 */
|
||
CMD_OFFSET_Y = 503, /**< 「偏移调整」:Y 轴偏移输入框。 */
|
||
CMD_OFFSET_TIME = 504, /**< 「偏移调整」:时间轴偏移输入框。 */
|
||
CMD_MIRROR_HORIZONTAL = 510, /**< 「镜像」二级面板:水平镜像(数据反序)。 */
|
||
CMD_MIRROR_VERTICAL = 511 /**< 「镜像」二级面板:垂直镜像(按 Y=0 上下翻转)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 左侧信号面板:按文件分组的信号树、可见性勾选与样式编辑。
|
||
*
|
||
* 面板只读取数据层(DataSet),全部显示配置读写均落在当前标签页(TabConfig)上,
|
||
* 因此不同标签页的信号勾选与样式互不影响。所有弹出菜单与拾色器均使用主题化自绘
|
||
* 浮动面板,不出现系统默认菜单样式。
|
||
*/
|
||
class SignalPanel
|
||
{
|
||
public:
|
||
SignalPanel();
|
||
|
||
/**
|
||
* @brief 绑定宿主窗口、字体、主题与共用浮动控件。
|
||
* @param [IN] host 宿主窗口句柄。
|
||
* @param [IN] fonts 字体集合,需在面板生命周期内保持有效。
|
||
* @param [IN] theme 主题配色,需在面板生命周期内保持有效。
|
||
* @param [IN] menu 共用的主题化弹出菜单,需在面板生命周期内保持有效。
|
||
* @param [IN] colorPicker 共用的浮动拾色器,需在面板生命周期内保持有效。
|
||
*/
|
||
void Setup(HWND host, const UiFonts* fonts, const Theme* theme, PopupMenu* menu,
|
||
ColorPickerPopup* colorPicker);
|
||
|
||
/**
|
||
* @brief 绑定数据层与当前标签页配置。
|
||
* @param [IN] dataSet 数据集合(可写,用于重命名信号与分组)。
|
||
* @param [IN] tab 当前标签页配置,允许为空。
|
||
*/
|
||
void SetContext(DataSet* dataSet, TabConfig* tab);
|
||
|
||
/**
|
||
* @brief 设置宿主客户区矩形(用于约束浮动面板位置)。
|
||
* @param [IN] bounds 客户区矩形。
|
||
*/
|
||
void SetClientBounds(const gd::RectF& bounds) { m_clientBounds = bounds; }
|
||
|
||
/**
|
||
* @brief 标记列表结构失效(数据或折叠状态变化后调用)。
|
||
*/
|
||
void InvalidateRows() { m_rowsDirty = true; }
|
||
|
||
/**
|
||
* @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] result 菜单选择结果。
|
||
* @return 需要重新布局返回 true。
|
||
*/
|
||
bool OnMenuCommand(const MenuResult& result);
|
||
|
||
/**
|
||
* @brief 判断面板是否处于内联文本输入状态。
|
||
* @return 正在输入返回 true。
|
||
*/
|
||
bool IsEditing() const { return m_editField != StyleEditField::NONE || m_filterEditing; }
|
||
|
||
/**
|
||
* @brief 判断坐标是否落在列表上方的「筛选信号」输入框内。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
* @return 落在输入框内返回 true。
|
||
*/
|
||
bool IsOnFilterBox(int x, int y) const;
|
||
|
||
/**
|
||
* @brief 进入筛选输入框的编辑状态(单击输入框时调用)。
|
||
*/
|
||
void BeginFilterEdit();
|
||
|
||
/**
|
||
* @brief 结束筛选输入框的编辑状态(点击别处或回车时调用)。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool EndFilterEdit();
|
||
|
||
/**
|
||
* @brief 向当前输入框送入一个字符。
|
||
* @param [IN] character 输入字符('\r' 提交、'\b' 退格、0x03 复制)。
|
||
* @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(int x, int y);
|
||
|
||
/**
|
||
* @brief 获取指定信号的显示外观(只读,全局共享)。
|
||
* @param [IN] ref 信号引用。
|
||
* @return 外观指针,引用无效时返回 nullptr。
|
||
*/
|
||
const SignalStyle* StyleOf(const SignalRef& ref) const;
|
||
|
||
/**
|
||
* @brief 获取指定信号的显示外观(可写,全局共享)。
|
||
* @param [IN] ref 信号引用。
|
||
* @return 外观指针,引用无效时返回 nullptr。
|
||
*/
|
||
SignalStyle* StyleOf(const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 获取当前选中信号(跨标签页共享,因此切换视图后选中项保持一致)。
|
||
* @return 信号引用。
|
||
*/
|
||
const SignalRef& SelectedRef() const;
|
||
|
||
/**
|
||
* @brief 设置当前选中信号(跨标签页共享)。
|
||
* @param [IN] ref 信号引用。
|
||
*/
|
||
void SelectSignal(const SignalRef& ref);
|
||
|
||
/**
|
||
* @brief 将浮动拾色器的取色结果应用到当前选中信号。
|
||
* @param [IN] color 0xAARRGGBB 颜色值。
|
||
* @return 应用成功返回 true。
|
||
*/
|
||
bool ApplyColor(std::uint32_t color);
|
||
|
||
/**
|
||
* @brief 关闭自定义颜色面板与线宽输入状态。
|
||
* @return 界面状态发生变化返回 true。
|
||
*/
|
||
bool CloseTransientUi();
|
||
|
||
/**
|
||
* @brief 取出并清除“需要重新布局”标记。
|
||
* @return 需要重新布局返回 true。
|
||
*/
|
||
bool TakeLayoutDirty();
|
||
|
||
/**
|
||
* @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。
|
||
* @note 需要按需重建列表行缓存(查 ✕ 的命中),因此不是 const。
|
||
*/
|
||
HCURSOR QueryCursor(int x, int y);
|
||
|
||
/**
|
||
* @brief 将列表滚动到指定信号所在位置。
|
||
* @param [IN] ref 目标信号引用。
|
||
*/
|
||
void ScrollToSignal(const SignalRef& ref);
|
||
|
||
private:
|
||
/**
|
||
* @brief 样式区与列表行中可进行内联文本编辑的字段。
|
||
*/
|
||
enum class StyleEditField : int
|
||
{
|
||
NONE = 0, /**< 未处于编辑状态。 */
|
||
WIDTH = 1, /**< 线宽数值。 */
|
||
OPACITY = 2, /**< 不透明度数值(百分比)。 */
|
||
SIGNAL_NAME = 3, /**< 信号显示名称(列表行内)。 */
|
||
FILE_NAME = 4 /**< 分组显示名称(列表行内)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 列表行的类型。
|
||
*/
|
||
enum class RowKind : int
|
||
{
|
||
GROUP = 0, /**< 文件分组标题行。 */
|
||
SIGNAL = 1 /**< 信号行。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 行的可交互区域。
|
||
*/
|
||
enum class RowPart : int
|
||
{
|
||
NONE = 0, /**< 未命中。 */
|
||
DISCLOSURE, /**< 折叠三角。 */
|
||
CHECK, /**< 复选框。 */
|
||
BODY, /**< 行主体。 */
|
||
CLOSE, /**< 文件行的关闭按钮。 */
|
||
LANE_BADGE, /**< 信号行的网格徽标。 */
|
||
REMOVE_SIGNAL /**< 信号行左侧的「移除该信号」✕ 按钮。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 样式编辑区中的可交互条目类型。
|
||
*/
|
||
enum class StyleHit : int
|
||
{
|
||
NONE = 0, /**< 未命中。 */
|
||
COLOR, /**< 预设颜色色块。 */
|
||
CUSTOM_CHIP, /**< 自定义颜色入口。 */
|
||
MODE, /**< 绘制样式按钮。 */
|
||
OPACITY_SLIDER, /**< 不透明度滑块。 */
|
||
OPACITY_VALUE, /**< 不透明度数值框(可点击输入)。 */
|
||
WIDTH_SLIDER, /**< 线宽滑块。 */
|
||
WIDTH_VALUE, /**< 线宽数值框(可点击输入)。 */
|
||
GAIN_ADJUST, /**< 「系数」按钮(点击展开系数二级面板)。 */
|
||
MIRROR_ADJUST, /**< 「镜像」按钮(点击展开镜像二级面板)。 */
|
||
ABSOLUTE_TOGGLE, /**< 「绝对值」切换按钮。 */
|
||
TOGGLE_ALL, /**< 全部显示/隐藏按钮。 */
|
||
FILTER_CLEAR /**< 「筛选信号」输入框右侧的清空 ✕。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 列表中的一行(文件分组或信号)。
|
||
*/
|
||
struct ListRow
|
||
{
|
||
RowKind kind = RowKind::SIGNAL; /**< 行类型。 */
|
||
int groupIndex = -1; /**< 所属文件下标。 */
|
||
int signalIndex = -1; /**< 信号下标(仅信号行有效)。 */
|
||
float height = 0.0f; /**< 行高(像素)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 重新构建列表行集合。
|
||
*/
|
||
void BuildRows();
|
||
|
||
/**
|
||
* @brief 在需要时重建列表行集合。
|
||
*/
|
||
void EnsureRows();
|
||
|
||
/**
|
||
* @brief 依据内容高度重算滚动条可见性与列表可用宽度。
|
||
*/
|
||
void RefreshScrollBar();
|
||
|
||
/**
|
||
* @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] row 行下标。
|
||
* @return 关闭按钮矩形。
|
||
*/
|
||
gd::RectF FileCloseRect(int row) const;
|
||
|
||
/**
|
||
* @brief 计算信号行网格徽标矩形。
|
||
* @param [IN] row 行下标。
|
||
* @return 徽标矩形。
|
||
*/
|
||
gd::RectF SignalBadgeRect(int row) const;
|
||
|
||
/**
|
||
* @brief 计算信号行右侧「移除该信号」✕ 按钮的矩形。
|
||
*
|
||
* 位于信号条目的**最右端**,尺寸、样式与右边距与分组行的关闭 ✕ 完全一致,
|
||
* 并与它们在同一行内垂直居中(因此视觉上垂直对齐);网格徽标会让位到它的左侧。
|
||
*
|
||
* @param [IN] row 行下标。
|
||
* @return 按钮矩形;非信号行时返回空矩形。
|
||
*/
|
||
gd::RectF SignalRemoveRect(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 判断哪个分组行需要「悬浮」在列表顶部(返回行下标,无需悬浮时返回 -1)。
|
||
*
|
||
* 判据:分组行已被滚出列表上边界(`RowRect(row).Y < m_listRect.Y`),
|
||
* 但该组内**仍有行留在列表里**(本组最后一行的下边界尚未滚过去)。
|
||
* 两者同时成立时组名必须钉在最上方;整组都滚过去后组名跟随一起消失。
|
||
*
|
||
* @param [IN] rowCount 行总数。
|
||
* @return 需要悬浮的分组行下标;无需悬浮返回 -1。
|
||
*/
|
||
int StickyGroupRowIndex(int rowCount) const;
|
||
|
||
/**
|
||
* @brief 把分组行「钉在列表顶部」绘制(组名跟随滚动,直到整组滚过去)。
|
||
*
|
||
* 用户要求长列表(例如上百个信号的 MF4)滚动时组名一直可见。
|
||
* ⚠️ 必须在常规行循环**之后**调用(它要盖在信号行之上),并且常规循环要**跳过**
|
||
* `row` 行,否则该行会被画两次。
|
||
*
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] row 需要悬浮的分组行下标(< 0 表示什么都不做)。
|
||
*/
|
||
void PaintStickyGroupRow(gd::Graphics& g, int row);
|
||
|
||
/**
|
||
* @brief 在列表区内绘制行内文本(**显式裁剪到列表矩形**)。
|
||
*
|
||
* ⚠️ `UiText` 走 GDI `DrawTextW`,**不遵循 GDI+ 的 `SetClip`**:列表需要滚动时,
|
||
* 被滚出视口的行(顶部部分可见的两三行)会把组名/信号名画到上方筛选框上。
|
||
* 因此行内文本一律经本函数绘制(见 `UiTextClipped`)。
|
||
*
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] text 文本内容。
|
||
* @param [IN] font 字体句柄。
|
||
* @param [IN] rect 文本布局矩形。
|
||
* @param [IN] argb 文本颜色。
|
||
* @param [IN] align 水平对齐方式。
|
||
*/
|
||
void PaintRowText(gd::Graphics& g, const std::wstring& text, HFONT font, const gd::RectF& rect,
|
||
std::uint32_t argb, TextAlign align);
|
||
|
||
/**
|
||
* @brief 在列表区内填充行内矩形(**夹取到列表矩形**)。
|
||
*
|
||
* ⚠️ `UiFillRect` 对大面积的**不透明**矩形会改走 GDI `FillRect`(同样不受 GDI+ 裁剪约束),
|
||
* 高 DPI 下行的面积足以超过该阈值 ⇒ 行背景/选中底色必须先把矩形夹到列表矩形。
|
||
*
|
||
* @param [IN,OUT] g 绘图上下文。
|
||
* @param [IN] rect 目标矩形。
|
||
* @param [IN] argb 填充颜色。
|
||
*/
|
||
void FillRowRect(gd::Graphics& g, const gd::RectF& rect, std::uint32_t argb);
|
||
|
||
/**
|
||
* @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 布置样式编辑区内各控件的矩形。
|
||
*/
|
||
void LayoutStyleControls();
|
||
|
||
/**
|
||
* @brief 取得样式编辑区固定所需高度。
|
||
* @return 逻辑高度(未乘缩放比例)。
|
||
*/
|
||
float DesiredStyleHeight() const;
|
||
|
||
/**
|
||
* @brief 弹出信号右键菜单。
|
||
* @param [IN] ref 目标信号引用。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
*/
|
||
void OpenSignalMenu(const SignalRef& ref, int x, int y);
|
||
|
||
/**
|
||
* @brief 弹出文件分组右键菜单。
|
||
* @param [IN] groupIndex 目标文件下标。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
*/
|
||
void OpenFileMenu(int groupIndex, int x, int y);
|
||
|
||
/**
|
||
* @brief 弹出网格归属菜单(由信号行的网格徽标触发)。
|
||
* @param [IN] ref 目标信号引用。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @param [IN] y 客户区纵坐标。
|
||
*/
|
||
void OpenLaneMenu(const SignalRef& ref, int x, int y);
|
||
|
||
/**
|
||
* @brief 打开浮动拾色器(由自定义颜色入口触发)。
|
||
* @param [IN] style 当前信号样式。
|
||
* @return 打开成功返回 true。
|
||
*/
|
||
bool OpenColorPickerPopup(const SignalStyle& style);
|
||
|
||
/**
|
||
* @brief 打开「系数」的**二级面板**(一个整行输入框 + 标题栏右侧的还原按钮)。
|
||
*
|
||
* 与自定义颜色入口同一套交互:点击按钮 → 抬起时展开浮动面板 → 面板内的输入框可编辑
|
||
* 绘制前乘上的系数(默认 1,至少显示两位小数),回车 / 点击其它位置提交,面板保持打开
|
||
*(与网格 Y 轴上下限的菜单内联输入框一致)。“还原”图标按钮把系数重置为 1。
|
||
*
|
||
* @return 面板已打开返回 true。
|
||
*/
|
||
bool OpenGainMenu();
|
||
|
||
/**
|
||
* @brief 打开「镜像」的**二级面板**(两行带复选框的选项:水平镜像 / 垂直镜像)。
|
||
*
|
||
* 两项都是“切换项”,点击后勾选状态立即生效且**菜单保持打开**,方便连续勾选;
|
||
* 只要任一项被勾选,「镜像」按钮即显示为激活态。
|
||
*
|
||
* @return 面板已打开返回 true。
|
||
*/
|
||
bool OpenMirrorMenu();
|
||
|
||
/**
|
||
* @brief 清除样式区的悬停高亮。
|
||
*
|
||
* 二级面板(弹出菜单)一打开就会接管鼠标事件,样式区不再收到 `WM_MOUSEMOVE`,
|
||
* 因此必须在打开面板时主动清掉悬停态,否则该按钮会一直保持"鼠标按住"般的高亮。
|
||
*/
|
||
void ClearStyleHover();
|
||
|
||
/**
|
||
* @brief 判断当前标签页中全部信号是否均为可见状态。
|
||
* @return 全部可见且至少有一条信号返回 true。
|
||
*/
|
||
bool AreAllSignalsVisible() const;
|
||
|
||
/**
|
||
* @brief 统计全部信号数量。
|
||
* @return 信号总数。
|
||
*/
|
||
int TotalSignalCount() const;
|
||
|
||
/**
|
||
* @brief 进入指定字段的内联输入状态。
|
||
* @param [IN] field 目标字段。
|
||
* @param [IN] row 列表行下标(仅行内字段有效)。
|
||
* @return 状态发生变化返回 true。
|
||
*/
|
||
bool BeginEdit(StyleEditField field, int row = -1);
|
||
|
||
/**
|
||
* @brief 结束内联输入状态。
|
||
* @param [IN] commit 是否提交输入值。
|
||
* @return 状态或取值发生变化返回 true。
|
||
*/
|
||
bool EndEdit(bool commit);
|
||
|
||
/**
|
||
* @brief 计算当前输入框的矩形。
|
||
* @return 输入框矩形,未处于编辑状态时返回空矩形。
|
||
*/
|
||
gd::RectF EditFieldRect() const;
|
||
|
||
/**
|
||
* @brief 计算「筛选信号」输入框的内容矩形(编辑与显示共用,保证不跳动)。
|
||
* @return 内容矩形。
|
||
*/
|
||
gd::RectF FilterFieldRect() const;
|
||
|
||
/**
|
||
* @brief 计算「筛选信号」输入框右侧清空按钮 ✕ 的矩形(无输入时为空矩形)。
|
||
* @return 清空按钮矩形。
|
||
*/
|
||
gd::RectF FilterClearRect() const;
|
||
|
||
/**
|
||
* @brief 清空筛选内容(点击 ✕ 时调用),并刷新列表。
|
||
*/
|
||
void ClearFilter();
|
||
|
||
/**
|
||
* @brief 计算指定行中名称区域的矩形(列表行内联编辑使用)。
|
||
* @param [IN] row 行下标。
|
||
* @return 名称区域矩形。
|
||
*/
|
||
gd::RectF RowNameRect(int row) const;
|
||
|
||
/**
|
||
* @brief 计算分组行中“组X”标签的像素宽度。
|
||
* @param [IN] row 行下标。
|
||
* @return 标签宽度。
|
||
*/
|
||
float FileGroupLabelWidth(int row) const;
|
||
|
||
/**
|
||
* @brief 调整当前信号不透明度。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @return 取值发生变化返回 true。
|
||
*/
|
||
bool ApplyOpacityFromX(float x);
|
||
|
||
/**
|
||
* @brief 依据线宽滑块横坐标调整当前信号线宽。
|
||
* @param [IN] x 客户区横坐标。
|
||
* @return 取值发生变化返回 true。
|
||
*/
|
||
bool ApplyWidthFromX(float x);
|
||
|
||
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
|
||
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
||
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
||
PopupMenu* m_menu = nullptr; /**< 共用弹出菜单。 */
|
||
ColorPickerPopup* m_colorPicker = nullptr; /**< 共用浮动拾色器。 */
|
||
const DataSet* m_dataSet = nullptr; /**< 数据集合(只读访问)。 */
|
||
DataSet* m_dataSetWritable = nullptr; /**< 数据集合(重命名等写入操作)。 */
|
||
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
|
||
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
||
gd::RectF m_clientBounds; /**< 宿主客户区矩形。 */
|
||
|
||
gd::RectF m_area; /**< 面板整体区域。 */
|
||
gd::RectF m_headerRect; /**< 标题区域。 */
|
||
gd::RectF m_filterRect; /**< 列表上方的「筛选信号」输入框区域。 */
|
||
gd::RectF m_listRect; /**< 列表区域。 */
|
||
gd::RectF m_styleRect; /**< 样式编辑区域。 */
|
||
gd::RectF m_toggleAllRect; /**< 全部显示/隐藏按钮。 */
|
||
gd::RectF m_scrollBarRect; /**< 滚动条区域。 */
|
||
|
||
std::array<gd::RectF, 12> m_colorRects; /**< 预设颜色色块矩形。 */
|
||
int m_colorCount = 0; /**< 实际可用的预设颜色数量。 */
|
||
gd::RectF m_customChipRect; /**< 自定义颜色入口矩形(与预设色块同尺寸)。 */
|
||
|
||
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
|
||
|
||
// 样式区固定五行:颜色 / 样式 / 调整(系数调整·取绝对值·中心反转)/ 线宽 / 透明度。
|
||
// 各行控件共用同一内容列宽度与左边界。
|
||
std::array<gd::RectF, 5> m_styleRowRects; /**< 五行整体矩形(含标签列)。 */
|
||
|
||
gd::RectF m_opacityTrackRect; /**< 不透明度滑块轨道矩形。 */
|
||
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
|
||
gd::RectF m_widthTrackRect; /**< 线宽滑块轨道矩形。 */
|
||
gd::RectF m_widthValueRect; /**< 线宽数值文本矩形(双击可输入)。 */
|
||
/** 「调整」行的三个等宽按钮:0 = 系数调整,1 = 取绝对值,2 = 中心反转。 */
|
||
std::array<gd::RectF, 3> m_adjustRects;
|
||
|
||
std::vector<ListRow> 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; /**< 列表滚动偏移量。 */
|
||
/**
|
||
* @brief 「组名悬浮」绘制用的行顶部坐标覆盖。
|
||
*
|
||
* 置位时 `RowRect` 忽略滚动偏移、直接返回 `m_rowYOverrideY` 作为行顶部,
|
||
* 从而使分组行(以及它派生的名称框 / ✕ / 徽标)整体画在钉住的位置。
|
||
* 只在使用期间短暂置位,绘制完立即复位(见 `PaintStickyGroupRow`)。
|
||
*/
|
||
bool m_rowYOverrideActive = false;
|
||
float m_rowYOverrideY = 0.0f;
|
||
|
||
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
|
||
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
|
||
bool m_draggingOpacity = false; /**< 是否正在拖拽不透明度滑块。 */
|
||
bool m_draggingWidth = false; /**< 是否正在拖拽线宽滑块。 */
|
||
int m_hoverRow = -1; /**< 当前悬停行。 */
|
||
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
|
||
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
|
||
int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */
|
||
|
||
StyleEditField m_editField = StyleEditField::NONE; /**< 当前处于输入状态的字段。 */
|
||
int m_editRow = -1; /**< 行内输入所作用的列表行下标。 */
|
||
TextEdit m_edit; /**< 输入框编辑模型(光标/选区/剪贴板)。 */
|
||
|
||
bool m_filterEditing = false; /**< 「筛选信号」输入框是否处于编辑状态。 */
|
||
std::wstring m_filterText; /**< 当前筛选文本(空串表示不过滤)。 */
|
||
TextEdit m_filterEdit; /**< 筛选输入框的编辑模型。 */
|
||
|
||
/**
|
||
* @brief 设置筛选文本并立即生效(重建行集合、回到顶部)。
|
||
* @param [IN] text 新的筛选文本(空串表示不过滤)。
|
||
*/
|
||
void ApplyFilterText(const std::wstring& text);
|
||
|
||
/**
|
||
* @brief 判断某信号的显示名是否匹配当前筛选表达式。
|
||
* @param [IN] signal 信号数据。支持 `*` 通配符,忽略大小写。
|
||
* @return 匹配(或未设置筛选)返回 true。
|
||
*/
|
||
bool FilterMatchesSignal(const SignalData& signal) const;
|
||
|
||
bool m_pendingLaneMenu = false; /**< 左键抬起时是否弹出网格归属菜单。 */
|
||
SignalRef m_pendingLaneRef; /**< 待弹出菜单的目标信号。 */
|
||
bool m_pendingColorPicker = false; /**< 左键抬起时是否弹出拾色器。 */
|
||
bool m_pendingGainMenu = false; /**< 左键抬起时是否弹出「系数」的二级面板。 */
|
||
bool m_pendingMirrorMenu = false; /**< 左键抬起时是否弹出「镜像」的二级面板。 */
|
||
|
||
/**
|
||
* @brief 待确认的「关闭类」按钮(✕)。
|
||
*
|
||
* 关闭类按钮统一改为**左键抬起**时生效:按下只记录目标,抬起时若指针仍在同一个
|
||
* ✕ 上才真正执行;按住后移开再松开视为放弃(与常见"可取消按钮"交互一致)。
|
||
*/
|
||
RowPart m_pendingClosePart = RowPart::NONE; /**< 待确认的 ✕ 所在行内区域(CLOSE / REMOVE_SIGNAL)。 */
|
||
int m_pendingCloseRow = -1; /**< 待确认的 ✕ 所在行下标。 */
|
||
bool m_pendingFilterClear = false; /**< 待确认的筛选框清空 ✕。 */
|
||
|
||
bool m_layoutDirty = false; /**< 是否需要重新布局。 */
|
||
|
||
SignalRef m_menuTargetSignal; /**< 菜单操作的目标信号。 */
|
||
int m_menuTargetFile = -1; /**< 菜单操作的目标文件。 */
|
||
};
|
||
|
||
} // namespace jdi
|
||
|
||
#endif // __SIGNAL_PANEL_HPP__
|