一、悬停高亮「几乎看不到、只是曲线换了颜色」(对比提交f845616的旧版观感) 根因:调色板第 1 色就是主题强调色(0xFF268BD2),而上一轮为提速把稠密信号的光晕改成 「按包络带原始几何 FillPath」——与曲线**逐像素同形**,于是曲线上的那层半透明强调色 完全看不出变化;旧版观感来自「lineWidth+4 的描边笔向两侧各外扩 2~3px」。 修法(PlotViewPaint.cpp::PaintSignalHaloFor): 1) 逐列成对填四边形,上下左右都外扩 grow = (lineWidth + 4) * scale / 2 (竖向跃变处旧版 5px 笔涂出的正是那条整高色带); 2) 不能把整条包络拼成一个多边形:满幅方波相邻列区间互相交叉,缠绕填充规则会在交叠处 相互抵消 ⇒ 填充变成一条条竖缝(实测只覆盖约一半带宽); 3) alpha 从 0x55 调回 0x66(与旧版描边笔一致)。 代价仍只在「换信号/换视界/改样式」时付一次,此后每帧只 AlphaBlend。 三维对照(新增 haloshot 探针 +f845616工作树构建):变化像素 旧 73726 / 上一轮 13529 / 本轮 74916,目视与旧版一致(跃变处的整高色带恢复)。 二、MATLAB .mat(v7.3 = HDF5)支持,含嵌套分组 新增只读 HDF5 子集(不引第三方库):Utilities/Inc/MatH5.hpp + Utilities/Src/MatH5.cpp - 超级块 v0/v2;对象头 v1(含续块,主块长度以 headerSize 为准);符号表(B-tree v1 + 本地堆)与 link 消息;数据空间 v1/v2;数据类型 v1;数据布局 v3(紧凑/连续/分块); 分块数据集支持 deflate(zlib);地址一律相对基地址解析。 新增 MATLAB 语义层:Utilities/Inc/MatLoader.hpp + Utilities/Src/MatLoader.cpp 1) Simulink SDI / SimulationData.Dataset(识别 #refs# + #sigstream#): · #refs# 元素有 ElementType(signal/dataset)、Name、Values; · timeseries 的 Values/DataR2、Values/TimeR2 是 #sigstream#/<n> 的流编号; #data# 是 uint8 字节流,按 8 字节一组解释为 double(与 #length#/#sampleSize# 一致); · 子数据集的 Values 是对象引用数组(子元素)⇒ 还原嵌套树; · 分组名 = 从根到信号的各级数据集名用 - 连接(如 A/A1/XYZ → 组 A-A1、信号 XYZ), 信号名取元素 Name(空则退回 timeseries 的 Name,再退回 信号N)。 2) 普通 v7.3 变量:顶层数值矩阵按「列 = 信号、行 = 采样」读取(时间轴 = 采样序号), 结构体按字段递归。 加载管线扩成多分组:新增 LoadSignalSourceGroups()(DataLoader)→ AsyncLoadState::groups → Application::LoadFile 逐个 AddGroup;其它格式仍只有一个分组。 文件对话框与 IsSupportedSignalFile 增加 .mat。 三、验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 haloshot(截图对照 f845616)、matcheck(15/15:4 组 8 信号、时间轴单调、采样数正确)、 matui(11/11:Application::LoadFile 端到端把 4 个分组加进数据层与信号列表); - 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、 panelstutter 8/8、stress 6/6; - make release / make debug 零警告;Makefile 与探针构建脚本补 -lz。 文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:叠加层必须自带外扩、 缠绕填充不可用于自交包络带、HDF5 主消息块长度看 headerSize、 MATLAB 分块维度可能比数据空间多一维、按右侧补 1 对齐)。
490 lines
18 KiB
C++
490 lines
18 KiB
C++
#ifndef __APPLICATION_HPP__
|
|
#define __APPLICATION_HPP__
|
|
|
|
#include <windows.h>
|
|
#include <objidl.h>
|
|
#include <gdiplus.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "ColorPickerPopup.hpp"
|
|
#include "DataModel.hpp"
|
|
#include "PaintUtil.hpp"
|
|
#include "PlotView.hpp"
|
|
#include "PopupMenu.hpp"
|
|
#include "SignalPanel.hpp"
|
|
#include "TabBar.hpp"
|
|
#include "Theme.hpp"
|
|
#include "Toolbar.hpp"
|
|
|
|
namespace jdi
|
|
{
|
|
|
|
/**
|
|
* @brief 应用主框架:负责窗口、布局、消息分发、标签页管理与绘制合成。
|
|
*
|
|
* 数据层(DataSet)全局共享,标签页仅保存各自的视图配置(TabConfig),
|
|
* 因此新标签页可以独立勾选信号、独立配色与分组,互不影响。
|
|
*/
|
|
class Application
|
|
{
|
|
public:
|
|
/**
|
|
* @brief 获取全局唯一应用实例。
|
|
* @return 应用实例引用。
|
|
*/
|
|
static Application& Instance();
|
|
|
|
/**
|
|
* @brief 初始化环境、创建窗口并进入消息循环。
|
|
* @param [IN] instance 模块实例句柄。
|
|
* @param [IN] showCommand 窗口显示方式。
|
|
* @return 进程退出码。
|
|
*/
|
|
int Run(HINSTANCE instance, int showCommand);
|
|
|
|
/**
|
|
* @brief 窗口过程中转处理函数。
|
|
* @param [IN] hwnd 窗口句柄。
|
|
* @param [IN] message 消息标识。
|
|
* @param [IN] wParam 消息参数。
|
|
* @param [IN] lParam 消息参数。
|
|
* @return 消息处理结果。
|
|
*/
|
|
LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
|
|
|
/**
|
|
* @brief 设置错误反馈是否使用模态对话框。
|
|
*
|
|
* 模态错误框会抢断用户当前操作;宿主在不需要打断(或无法交互)的场景下
|
|
* 应关闭它,此时错误信息改为在绘图区浮层中非模态展示。
|
|
*
|
|
* @param [IN] enabled true 表示允许弹出模态错误框。
|
|
*/
|
|
void SetModalErrorsEnabled(bool enabled) { m_modalErrorsEnabled = enabled; }
|
|
|
|
/**
|
|
* @brief 查询错误反馈是否使用模态对话框。
|
|
* @return 允许弹出模态错误框返回 true。
|
|
*/
|
|
bool ModalErrorsEnabled() const { return m_modalErrorsEnabled; }
|
|
|
|
private:
|
|
/**
|
|
* @brief 鼠标交互的作用目标。
|
|
*/
|
|
enum class Target : int
|
|
{
|
|
NONE = 0, /**< 无目标。 */
|
|
TAB_BAR, /**< 标签栏。 */
|
|
TOOLBAR, /**< 工具栏。 */
|
|
PANEL, /**< 信号面板。 */
|
|
PLOT, /**< 波形绘图区。 */
|
|
SPLITTER, /**< 面板分隔条。 */
|
|
EDIT_FIELD /**< 内联输入框(拖拽框选选区)。 */
|
|
};
|
|
|
|
Application() = default;
|
|
~Application() = default;
|
|
Application(const Application&) = delete;
|
|
Application& operator=(const Application&) = delete;
|
|
|
|
/**
|
|
* @brief 窗口过程静态回调。
|
|
* @param [IN] hwnd 窗口句柄。
|
|
* @param [IN] message 消息标识。
|
|
* @param [IN] wParam 消息参数。
|
|
* @param [IN] lParam 消息参数。
|
|
* @return 消息处理结果。
|
|
*/
|
|
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
|
|
|
/**
|
|
* @brief 创建主窗口与相关资源。
|
|
* @param [IN] instance 模块实例句柄。
|
|
* @param [IN] showCommand 窗口显示方式。
|
|
* @return 创建成功返回 true。
|
|
*/
|
|
bool Initialize(HINSTANCE instance, int showCommand);
|
|
|
|
/**
|
|
* @brief 释放窗口与绘图资源。
|
|
*/
|
|
void Shutdown();
|
|
|
|
/**
|
|
* @brief 依据 DPI 重建字体与布局。
|
|
* @param [IN] dpi 目标 DPI。
|
|
*/
|
|
void ApplyDpi(UINT dpi);
|
|
|
|
/**
|
|
* @brief 重新计算各区域的布局。
|
|
*/
|
|
void Relayout();
|
|
|
|
/**
|
|
* @brief 创建或调整后备缓冲区尺寸。
|
|
* @param [IN] width 客户区宽度。
|
|
* @param [IN] height 客户区高度。
|
|
*/
|
|
void EnsureBackBuffer(int width, int height);
|
|
|
|
/**
|
|
* @brief 释放后备缓冲区。
|
|
*/
|
|
void ReleaseBackBuffer();
|
|
|
|
/**
|
|
* @brief 完整重绘客户区并呈现到窗口。
|
|
*/
|
|
void Render();
|
|
|
|
/**
|
|
* @brief 获取当前激活标签页配置(可写)。
|
|
* @return 激活标签页配置引用。
|
|
*/
|
|
TabConfig& ActiveTab();
|
|
|
|
/**
|
|
* @brief 获取当前激活标签页配置(只读)。
|
|
* @return 激活标签页配置引用。
|
|
*/
|
|
const TabConfig& ActiveTab() const;
|
|
|
|
/**
|
|
* @brief 新建一个标签页并切换到它。
|
|
* @param [IN] collapseGroups 是否把信号列表里的分组**默认折起**
|
|
*(用户新建的标签页传 true;启动时的首个标签页传 false,便于直接勾选信号)。
|
|
*/
|
|
void NewTab(bool collapseGroups);
|
|
|
|
/**
|
|
* @brief 生成当前未被占用的最小标签页名称(关闭标签页后编号可被复用)。
|
|
* @return 形如「视图 N」的名称。
|
|
*/
|
|
std::wstring NextTabName() const;
|
|
|
|
/**
|
|
* @brief 关闭指定标签页。
|
|
* @param [IN] index 标签页下标。
|
|
*/
|
|
void CloseTab(int index);
|
|
|
|
/**
|
|
* @brief 切换激活标签页并重新绑定各组件上下文。
|
|
* @param [IN] index 标签页下标。
|
|
*/
|
|
void SetActiveTab(int index);
|
|
|
|
/**
|
|
* @brief 将标签栏内容与当前标签页集合同步。
|
|
*/
|
|
void RefreshTabBar();
|
|
|
|
/**
|
|
* @brief 检测激活标签页的可见性/网格变化,并同步绘图区与布局。
|
|
* @param [IN] force 是否强制同步(忽略缓存比对)。
|
|
*/
|
|
void SynchronizeView(bool force);
|
|
|
|
/**
|
|
* @brief 将全部标签页配置与数据层对齐(分组增删后调用)。
|
|
*/
|
|
void SyncTabsToDataSet();
|
|
|
|
/**
|
|
* @brief 从工作区移除指定分组并修正各标签页配置。
|
|
* @param [IN] groupIndex 分组下标。
|
|
*/
|
|
void RemoveGroup(int groupIndex);
|
|
|
|
/**
|
|
* @brief 从工作区移除某分组中的一条信号,并立即释放其采样内存。
|
|
*
|
|
* 用户点击信号行左侧的 ✕ 时调用:该信号一旦被确认不再需要,就应从数据层删除
|
|
* 并释放内存(而非只是隐藏)。若它是所属分组中最后一条信号,则连同分组一起移除。
|
|
*
|
|
* @param [IN] groupIndex 信号所属分组下标。
|
|
* @param [IN] signalIndex 组内信号下标。
|
|
*/
|
|
void RemoveSignal(int groupIndex, int signalIndex);
|
|
|
|
/**
|
|
* @brief 处理标签栏交互结果。
|
|
* @param [IN] result 交互结果。
|
|
*/
|
|
void OnTabBarResult(const TabBarResult& result);
|
|
|
|
/**
|
|
* @brief 弹出标签页右键菜单(主题化自绘菜单)。
|
|
* @param [IN] result 标签栏交互结果。
|
|
*/
|
|
void ShowTabContextMenu(const TabBarResult& result);
|
|
|
|
/**
|
|
* @brief 弹出绘图区网格右键菜单(设置 Y 轴上下限 / 恢复自适应)。
|
|
* @param [IN] laneIndex 目标网格下标。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
*/
|
|
void ShowPlotContextMenu(int laneIndex, int x, int y);
|
|
|
|
/**
|
|
* @brief 把网格菜单上的 Y 轴上下限显示值刷新为数据层的真实值(菜单保持打开)。
|
|
* @param [IN] laneIndex 网格下标。
|
|
*/
|
|
void RefreshPlotMenuValues(int laneIndex);
|
|
|
|
/**
|
|
* @brief 派发弹出菜单的选择结果。
|
|
* @param [IN] result 菜单选择结果。
|
|
*/
|
|
void DispatchMenuResult(const MenuResult& result);
|
|
|
|
/**
|
|
* @brief 调整标签页顺序(拖拽重排)。
|
|
* @param [IN] from 被移动的标签页下标。
|
|
* @param [IN] target 目标插入位置(插入到该下标之前)。
|
|
*/
|
|
void MoveTab(int from, int target);
|
|
|
|
/**
|
|
* @brief 应用绘图区提交的信号重命名请求。
|
|
* @return 应用了重命名返回 true。
|
|
*/
|
|
bool ApplyPendingRename();
|
|
|
|
/**
|
|
* @brief 应用绘图区提交的「实验性功能 → 构建为新信号」请求。
|
|
*
|
|
* 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱峰调整),
|
|
* 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱峰调整 M%)`,
|
|
* 插入到原信号所在组的原信号位置**下方**,并把新信号所在的网格移动到原信号网格的下方。
|
|
*
|
|
* @return 实际构建了新信号返回 true。
|
|
*/
|
|
bool ApplyPendingDerivedBuild();
|
|
|
|
/**
|
|
* @brief 应用绘图区提交的「把信号列表滚动到目标信号」的联动请求。
|
|
*
|
|
* 用户 2026-10-15 要求:在绘图区触发联动(点击图例 / 点击信号曲线等把某条信号选为
|
|
* 当前目标)时,信号选择区域要**平滑滚动**到该信号并完整显示它
|
|
*(信号很多时否则找不见选中的那一条)。
|
|
*
|
|
* @return 存在并应用了请求返回 true。
|
|
*/
|
|
bool ApplyPendingPanelScroll();
|
|
|
|
/**
|
|
* @brief 应用信号面板提交的「把绘图区滚动到目标网格」的联动请求。
|
|
*
|
|
* 用户 2026-10-16 要求:点击信号选择区域的**信号名**(或样式区「目标」行的信号名)时,
|
|
* 若该信号所在的网格不在绘图区可视区内,就自动调节绘图区的滚动条让该网格完整出现
|
|
*(上百个独立网格时,仅靠网格高亮无法定位到画面之外的网格)。悬停不触发。
|
|
*
|
|
* @return 存在请求并已应用返回 true。
|
|
*/
|
|
bool ApplyPendingPlotLaneScroll();
|
|
|
|
/**
|
|
* @brief 派生信号构建请求(多次迭代均值滤波 / 弱峰调整共用同一套插入流程)。
|
|
*/
|
|
struct DerivedSignalRequest
|
|
{
|
|
int groupIndex = -1; /**< 原信号所属分组下标。 */
|
|
int signalIndex = -1; /**< 原信号下标。 */
|
|
int points = 5; /**< 多次迭代均值滤波:均值点数 X(滑平半径)。 */
|
|
int iterations = 1; /**< 多次迭代均值滤波:迭代次数 N(至少 1)。 */
|
|
int amountPercent = 0; /**< 弱峰调整:调整幅度(-400–400,默认 0)。 */
|
|
bool weakPeak = false; /**< true = 弱峰调整,false = 多次迭代均值滤波。 */
|
|
};
|
|
|
|
/**
|
|
* @brief 把一条派生信号(多次迭代均值滤波 / 弱峰调整)插入数据层并同步各视图。
|
|
* @param [IN] request 构建请求。
|
|
* @param [IN] makeVisible 是否在当前标签页中勾选新信号。
|
|
* @return 构建并插入成功返回 true。
|
|
*/
|
|
bool BuildDerivedSignal(const DerivedSignalRequest& request, bool makeVisible);
|
|
|
|
/**
|
|
* @brief 弹出“打开数据文件”对话框并加载所选文件。
|
|
*/
|
|
void OpenFileDialog();
|
|
|
|
/**
|
|
* @brief 加载指定的数据文件并刷新界面。
|
|
* @param [IN] path 文件完整路径。
|
|
* @return 加载成功返回 true。
|
|
*/
|
|
bool LoadFile(const std::wstring& path);
|
|
|
|
/**
|
|
* @brief 执行工具栏动作。
|
|
* @param [IN] action 动作标识。
|
|
*/
|
|
void ExecuteToolbarAction(ToolbarAction action);
|
|
|
|
/**
|
|
* @brief 处理键盘按键。
|
|
* @param [IN] key 虚拟键码。
|
|
* @return 已处理返回 true。
|
|
*/
|
|
bool HandleKeyDown(WPARAM key);
|
|
|
|
/**
|
|
* @brief 报告操作结果信息。
|
|
* @param [IN] text 提示文本。
|
|
* @param [IN] isError 是否为错误提示。
|
|
* @note 界面不再保留常驻状态栏:错误以对话框提示,普通提示被忽略,避免无用信息干扰。
|
|
*/
|
|
void SetStatusMessage(const std::wstring& text, bool isError);
|
|
|
|
/**
|
|
* @brief 刷新窗口标题。
|
|
*/
|
|
void UpdateWindowTitle();
|
|
|
|
/**
|
|
* @brief 解析命令行中的文件参数并加载。
|
|
*/
|
|
void LoadCommandLineFiles();
|
|
|
|
/**
|
|
* @brief 在后台线程解析并构建分组数据,同时播放加载过渡动画(界面不卡死)。
|
|
*
|
|
* 大文件的瓶颈在“解析 CSV”与“按列重组采样数据”,这两步都不依赖 DataSet,
|
|
* 因此放在工作线程执行;主线程循环抽送消息并重绘加载遮罩层,
|
|
* 直到后台完成后再把结果移动进 DataSet(O(1),不会引起卡顿)。
|
|
*
|
|
* @param [IN] path 文件路径。
|
|
* @param [OUT] groups 构建好的信号分组(多数文件只有 1 个;`.mat` 的嵌套数据集会有多个)。
|
|
* @param [OUT] error 失败原因描述。
|
|
* @return 加载成功返回 true;用户取消或失败返回 false。
|
|
*/
|
|
bool LoadFileAsync(const std::wstring& path, std::vector<SignalGroup>& groups, std::wstring& error);
|
|
|
|
/**
|
|
* @brief 绘制加载过渡动画遮罩(在 Render 的最后一步调用,覆盖整个客户区)。
|
|
* @param [IN,OUT] g 绘图上下文。
|
|
* @param [IN] width 客户区宽度。
|
|
* @param [IN] height 客户区高度。
|
|
*/
|
|
void PaintLoadingOverlay(gd::Graphics& g, int width, int height);
|
|
|
|
/**
|
|
* @brief 处理客户区鼠标移动。
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
* @param [IN] buttonDown 是否有按键处于按下状态。
|
|
* @return 需要重绘返回 true。
|
|
*/
|
|
bool HandleMouseMove(int x, int y, bool buttonDown);
|
|
|
|
/**
|
|
* @brief 拖动(鼠标捕获)期间主动刷新光标形状。
|
|
*
|
|
* Windows 在鼠标被捕获时不再派发 `WM_SETCURSOR`,若不主动设置,
|
|
* 光标会一直停留在按下瞬间的形状(例如选区放大时看不到十字/方向箭头)。
|
|
*
|
|
* @param [IN] x 客户区横坐标。
|
|
* @param [IN] y 客户区纵坐标。
|
|
*/
|
|
void UpdateDragCursor(int x, int y);
|
|
|
|
HINSTANCE m_instance = nullptr; /**< 模块实例句柄。 */
|
|
HWND m_hwnd = nullptr; /**< 主窗口句柄。 */
|
|
UINT m_dpi = 96; /**< 当前 DPI。 */
|
|
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
|
ULONG_PTR m_gdiplusToken = 0; /**< GDI+ 运行时令牌。 */
|
|
bool m_gdiplusReady = false; /**< GDI+ 是否初始化成功。 */
|
|
|
|
Theme m_theme; /**< 主题配色。 */
|
|
UiFonts m_fonts; /**< 界面字体。 */
|
|
PopupMenu m_menu; /**< 与主题一致的自绘弹出菜单。 */
|
|
ColorPickerPopup m_colorPicker; /**< 与主题一致的浮动拾色器。 */
|
|
DataSet m_dataSet; /**< 全局共享的数据集合。 */
|
|
std::vector<TabConfig> m_tabs; /**< 标签页配置集合。 */
|
|
int m_activeTab = -1; /**< 当前激活标签页下标。 */
|
|
|
|
TabBar m_tabBar; /**< 顶部标签栏。 */
|
|
Toolbar m_toolbar; /**< 顶部工具栏。 */
|
|
SignalPanel m_panel; /**< 左侧信号面板。 */
|
|
PlotView m_plot; /**< 波形绘图区。 */
|
|
|
|
gd::RectF m_tabBarRect; /**< 标签栏区域。 */
|
|
gd::RectF m_toolbarRect; /**< 工具栏区域。 */
|
|
gd::RectF m_panelRect; /**< 信号面板区域。 */
|
|
gd::RectF m_splitterRect; /**< 面板分隔条区域。 */
|
|
gd::RectF m_plotRect; /**< 绘图区区域。 */
|
|
|
|
float m_panelWidth = 366.0f; /**< 面板宽度(逻辑像素)。 */
|
|
bool m_draggingSplitter = false; /**< 是否正在拖动分隔条。 */
|
|
bool m_middlePanning = false; /**< 是否正在用鼠标中键自由平移绘图区。 */
|
|
int m_splitterGrabOffset = 0; /**< 分隔条拖动的抓取偏移。 */
|
|
|
|
HDC m_backDc = nullptr; /**< 后备缓冲设备上下文。 */
|
|
HBITMAP m_backBitmap = nullptr; /**< 后备缓冲位图。 */
|
|
HGDIOBJ m_backOldBitmap = nullptr; /**< 后备缓冲原始位图。 */
|
|
int m_backWidth = 0; /**< 后备缓冲宽度。 */
|
|
int m_backHeight = 0; /**< 后备缓冲高度。 */
|
|
|
|
Target m_activeTarget = Target::NONE; /**< 当前鼠标作用目标。 */
|
|
bool m_trackingLeave = false; /**< 是否已注册鼠标离开通知。 */
|
|
bool m_hintTimerActive = false; /**< 提示浮层淡出定时器是否已启动。 */
|
|
/** 信号列表「滚动到目标信号」动画的定时器是否已启动(见 `SignalPanel::AdvanceScrollAnimation`)。 */
|
|
bool m_scrollTimerActive = false;
|
|
/**
|
|
* 错误反馈是否使用模态对话框(默认开启;`Initialize` 遇到隐藏窗口时自动关闭)。
|
|
*
|
|
* 隐藏窗口(无人可点确定)下弹模态框等于把进程卡死,因此按启动方式自动降级为
|
|
* 绘图区非模态浮层提示;调用方也可用 `SetModalErrorsEnabled` 显式控制。
|
|
*/
|
|
bool m_modalErrorsEnabled = true;
|
|
std::wstring m_windowTitleText; /**< 上次写入窗口标题的完整文本(用于避免重复设置)。 */
|
|
int m_tabMenuIndex = -1; /**< 标签栏右键菜单的目标标签页下标。 */
|
|
int m_plotMenuLane = -1; /**< 绘图区右键菜单的目标网格下标。 */
|
|
|
|
int m_cachedVisibleCount = -1; /**< 上次同步时的可见信号数量。 */
|
|
std::size_t m_cachedLaneCount = 0; /**< 上次同步时的网格数量。 */
|
|
bool m_cachedAnyVisible = false; /**< 上次同步时是否存在可见信号。 */
|
|
|
|
bool m_loading = false; /**< 是否正在显示加载过渡动画。 */
|
|
std::wstring m_loadingText; /**< 加载遮罩显示的文件名。 */
|
|
float m_loadingProgress = 0.0f; /**< 加载进度(0-1)。 */
|
|
/** 已读取字节数(用于进度浮层);与 `m_loadingTotalBytes` 一起展示“已读 / 总量”。 */
|
|
unsigned long long m_loadingBytesRead = 0;
|
|
/** 文件总字节数(0 表示未知)。 */
|
|
unsigned long long m_loadingTotalBytes = 0;
|
|
float m_loadingSpin = 0.0f; /**< 加载动画旋转相位(度)。 */
|
|
/**
|
|
* @brief 装饰区域(标签栏 / 工具栏 / 信号面板 / 分隔条)是否需要重绘。
|
|
*
|
|
* 这些区域的绘制成本与数据量无关但与控件数量成正比,且只在鼠标位于其中
|
|
* 或发生交互时才会变化。绘图区拖拽(如双光标左右移动)期间持续置为
|
|
* false,从而每帧只重绘、只回送绘图区所在的矩形,避免整窗重绘的开销。
|
|
*/
|
|
bool m_chromeDirty = true;
|
|
/**
|
|
* @brief 「只有信号面板(与分隔条)需要重绘」。
|
|
*
|
|
* 面板滚动动画(「滚动到目标信号」)每帧只改变面板像素,而 `m_chromeDirty` 会
|
|
* 连**标签栏 / 工具栏 / 整窗底色**一起重绘 —— 实测 129 个网格时这一份装饰重绘
|
|
* 约 **3~4ms/帧**,会把动画挤出 15.6ms 的预算(用户 2026-10-17 报告:滚动过渡动画卡顿)。
|
|
*
|
|
* ✓ 由滚动定时器在**只有面板动画在跑**时置位:`Render` 只重绘面板 + 分隔条
|
|
*(标签栏/工具栏/整窗底色与上一帧逐像素相同,后备缓冲区里的旧像素本来就是对的)。
|
|
* ⚠️ 回送仍是 `if (fullFrame) {整窗} else {绘图区}` 的**无条件分支**,
|
|
* 与 §9.41 的教训一致(**不**按更新区域决定回送范围)。
|
|
*/
|
|
bool m_panelDirty = false;
|
|
bool m_mouseOverPlot = false; /**< 上一次鼠标移动是否位于绘图区内。 */
|
|
};
|
|
|
|
} // namespace jdi
|
|
|
|
#endif // __APPLICATION_HPP__
|