1) 光标 2 的样式与光标 1 完全一致,并改用更鲜艳的颜色 - 原先光标 2 用朝上的三角把手,观感突兀;现两条光标统一为同款朝下三角 - 主题新增 cursorAlt 并改用 Solarized 洋红 #D33682(原青色偏灰),与光标 1 的 橙色 #CB4B16 同属暖色且更醒目,靠颜色而非形状区分 2) 光标允许滞留在网格可视区域之外(不再被强行夹回视野) - 删除"视图变化后夹取光标"的逻辑(时间轴选区放大、适应视图、按网格时间轴展开、 平移时都不再移动光标);仅在拖拽光标本身时才使用 ClampCursorToView(which) - 新增 CursorDisplayX(which):光标在视野外时把它的显示位置"钉"在最近的网格边缘, 取值标注与时间读数因此始终可见;数据点改为边缘三角指示(指向光标实际方向) - 命中测试改用"显示横坐标",因此被钉在边缘的光标仍可抓住拖回 3) 光标读数增加细描边,避免与背景/曲线颜色过于接近 - 新增 DrawLabelOutline():按文字颜色取反色做 1px 半透明描边, 网格内的取值胶囊、光标时间读数、差值标签统一使用 4) 双光标差值显示(时间轴上方居中,自适应单位,可拖拽、可编辑) - 新增 FormatDuration()/FormatDurationNumber()/ChosenDurationUnit(): 在 h / min / s / ms / us / ns / ps 之间自动选择单位(数值落在 [1,1000)) - 差值标签位于两条时间读数的中点,样式与时间读数一致 - 双击进入编辑:输入值采用**当前显示单位**;确认后**前方(时间较小)光标不动**, 后方光标移动到"前方 + 输入差值" - 标签支持左右拖拽(新增 PlotDragMode::CURSOR_SPAN_MOVE):两条光标整体平移、间隔不变 5) 双光标内容重叠时的层次与差值标签位置 - 新增 m_activeCursor:最后操作过(拖拽/单击/编辑)的光标其内容绘制在上层, 两条光标的竖线、取值胶囊、时间读数都按此顺序绘制 - 差值标签改为按**两条时间读数的实际净空**判断:放不下时自动落到主时间轴内部 (原先按光标线间距判断,光标被钉到边缘时会用差值标签盖住时间读数) 6) 选区放大的光标形状与颜色 - 未开始框选时不再改变鼠标形状(保持箭头);按下但未拖动时为十字 IDC_CROSS; 确定为时间轴选区后为左右箭头、Y 轴选区后为上下箭头 - 两种方向的选区统一使用强调色(原先 Y 轴选区用警示红,观感不一致) - 新增 Application::UpdateDragCursor():Windows 在鼠标捕获期间不派发 WM_SETCURSOR, 必须在 WM_MOUSEMOVE / WM_LBUTTONDOWN 后主动刷新光标形状,否则形状不会变化 7) 提示浮层加大并支持"停留后淡出",光标模式不再提示 - 提示字号由 uiSmall 改为 ui,胶囊 30px 高、内边距更大 - 新增停留 1300ms + 淡出 500ms 的时间控制;宿主用 WM_TIMER(33ms) 驱动重绘, 淡出结束自动 KillTimer(文字走 GDI 不支持 alpha,改为向胶囊底色混色实现淡出) - 「光标」三段式切换不再显示任何提示 验证([1].CSV 11 信号,窗口 1725x996,125% DPI): - 双光标:橙 0.63 / 洋红 0.7812,同款把手;底部时间读数 + 居中差值 "151.2 ms" - 放大到不含光标的区间:光标不移动,取值以边缘三角 + 胶囊贴边显示 - 拖拽差值标签:两光标从 0.63/0.7812 整体平移到 0.424202/0.575402,差值仍为 151.2 ms - 差值编辑:输入 300(显示单位为 ms)→ 前方光标保持 0.63,后方移动到 0.93 - 重叠层次:差值设为 0 时洋红(活动)在上;单击光标线后橙色(活动)在上 - 光标形状:未框选 ARROW / 按下 CROSS / 横向 SIZEWE / 纵向 SIZENS(cursor.ps1 实测) - 提示浮层:出现后约 1.3s 开始淡出、约 1.8s 消失;右键放弃选区后视图不变且模式保持选中 - 空闲 CPU 0.00%;make clean; make all 无警告无错误
371 lines
12 KiB
C++
371 lines
12 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);
|
|
|
|
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 新建标签页并切换为激活页。
|
|
*/
|
|
void NewTab();
|
|
|
|
/**
|
|
* @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] fileIndex 文件下标。
|
|
*/
|
|
void RemoveFile(int fileIndex);
|
|
|
|
/**
|
|
* @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 弹出文件选择对话框并加载所选文件。
|
|
*/
|
|
void OpenFileDialog();
|
|
|
|
/**
|
|
* @brief 加载指定 CSV 文件并刷新界面。
|
|
* @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] file 构建好的数据文件。
|
|
* @param [OUT] error 失败原因描述。
|
|
* @return 加载成功返回 true;用户取消或失败返回 false。
|
|
*/
|
|
bool LoadFileAsync(const std::wstring& path, DataFile& file, 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; /**< 是否正在拖动分隔条。 */
|
|
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; /**< 提示浮层淡出定时器是否已启动。 */
|
|
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)。 */
|
|
float m_loadingSpin = 0.0f; /**< 加载动画旋转相位(度)。 */
|
|
unsigned long long m_loadingBytes = 0; /**< 当前文件字节数(用于展示体积)。 */
|
|
};
|
|
|
|
} // namespace jdi
|
|
|
|
#endif // __APPLICATION_HPP__
|