feat: 重构绘图区网格布局与信号拖拽,精简界面信息并新增 Y 轴精确控制
网格与标尺: - 消除网格之间无意义的间隔,改用较重的横向分界线明确区分相邻网格 - 纵轴标尺从绘图区左侧外侧移入网格内侧,并改为带半透明衬底的覆盖层绘制, 使其在波形之上依然清晰可读;绘图区左侧空间因此完全还给波形 - 继续保证底部时间轴预留高度,并把时间列名贴左显示、避开刻度标签 常规模式拖拽: - 常规模式下水平拖动平移时间轴,纵向拖动偏移当前网格的幅值显示位置 - 绘图区右键菜单新增「恢复 Y 轴自适应」「设置 Y 轴最大值…」「设置 Y 轴最小值…」 「全部自适应」,上下限通过内联输入框直接键入(与线宽输入一致的交互) 信号拖拽: - 鼠标贴近信号曲线时给出光晕高亮与提示气泡(与时间光标的提示方式一致) - 仅在出现提示时按左键才会进入拖拽;拖拽过程中在目标网格内绘制曲线虚影、 高亮目标网格边框并提示「移动到网格 N」,松开左键确认归属变更 - 命中检测基于折线到光标的精确点到线段距离,散点模式按采样点距离判定 界面精简: - 移除底部状态栏(左下角统计与右下角提示均不再占用空间),错误改由对话框提示 - 标题栏只显示应用名称 - 移除工具栏右上角的视图时间范围文本;按钮除「导入文件」外整体靠右排列 其他: - 新增 PlotView::PaintEditField 统一绘制时间光标与网格 Y 轴上下限的输入框 - 移除已无引用的 UiDrawDashedLine 与绘图区标尺占位宽度
This commit is contained in:
1 parent
17eb03a1b3
commit
542aa51e40
9 files changed
+981
-335
No files matched your search
@@ -124,12 +124,6 @@ namespace jdi
|
||||
*/
|
||||
void Render();
|
||||
|
||||
/**
|
||||
* @brief 绘制底部状态栏。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
*/
|
||||
void PaintStatusBar(gd::Graphics& g);
|
||||
|
||||
/**
|
||||
* @brief 获取当前激活标签页配置(可写)。
|
||||
* @return 激活标签页配置引用。
|
||||
@@ -199,6 +193,14 @@ namespace jdi
|
||||
*/
|
||||
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 派发弹出菜单的选择结果。
|
||||
* @param [IN] result 菜单选择结果。
|
||||
@@ -231,9 +233,10 @@ namespace jdi
|
||||
bool HandleKeyDown(WPARAM key);
|
||||
|
||||
/**
|
||||
* @brief 设置状态栏提示信息。
|
||||
* @brief 报告操作结果信息。
|
||||
* @param [IN] text 提示文本。
|
||||
* @param [IN] isError 是否为错误提示。
|
||||
* @note 界面不再保留常驻状态栏:错误以对话框提示,普通提示被忽略,避免无用信息干扰。
|
||||
*/
|
||||
void SetStatusMessage(const std::wstring& text, bool isError);
|
||||
|
||||
@@ -281,7 +284,6 @@ namespace jdi
|
||||
gd::RectF m_panelRect; /**< 信号面板区域。 */
|
||||
gd::RectF m_splitterRect; /**< 面板分隔条区域。 */
|
||||
gd::RectF m_plotRect; /**< 绘图区区域。 */
|
||||
gd::RectF m_statusRect; /**< 状态栏区域。 */
|
||||
|
||||
float m_panelWidth = 366.0f; /**< 面板宽度(逻辑像素)。 */
|
||||
bool m_draggingSplitter = false; /**< 是否正在拖动分隔条。 */
|
||||
@@ -296,8 +298,7 @@ namespace jdi
|
||||
Target m_activeTarget = Target::NONE; /**< 当前鼠标作用目标。 */
|
||||
bool m_trackingLeave = false; /**< 是否已注册鼠标离开通知。 */
|
||||
int m_tabMenuIndex = -1; /**< 标签栏右键菜单的目标标签页下标。 */
|
||||
std::wstring m_statusMessage; /**< 状态栏提示文本。 */
|
||||
bool m_statusIsError = false; /**< 状态提示是否为错误。 */
|
||||
int m_plotMenuLane = -1; /**< 绘图区右键菜单的目标网格下标。 */
|
||||
|
||||
int m_cachedVisibleCount = -1; /**< 上次同步时的可见信号数量。 */
|
||||
std::size_t m_cachedLaneCount = 0; /**< 上次同步时的网格数量。 */
|
||||
|
||||
@@ -24,16 +24,13 @@ namespace jdi
|
||||
namespace
|
||||
{
|
||||
const wchar_t* kWindowClassName = L"JustDataInspectorMainWindow"; /**< 窗口类名。 */
|
||||
const wchar_t* kWindowTitle = L"Just DataInspector"; /**< 默认窗口标题。 */
|
||||
const float kStatusBarHeight = 26.0f; /**< 状态栏高度(逻辑像素)。 */
|
||||
const wchar_t* kWindowTitle = L"Just DataInspector"; /**< 窗口标题(仅显示应用名称)。 */
|
||||
const float kSplitterWidth = 4.0f; /**< 分隔条宽度(逻辑像素)。 */
|
||||
const float kMinPanelWidth = 260.0f; /**< 面板最小宽度(逻辑像素)。 */
|
||||
const float kMaxPanelWidth = 400.0f; /**< 面板最大宽度(逻辑像素)。 */
|
||||
const int kMinClientWidth = 860; /**< 窗口最小客户区宽度。 */
|
||||
const int kMinClientHeight = 540; /**< 窗口最小客户区高度。 */
|
||||
const UINT kMessageRemoveFile = WM_APP + 1; /**< 面板请求移除文件的自定义消息。 */
|
||||
const wchar_t* kStatusHint =
|
||||
L"滚轮纵向滚动网格 · Shift+滚轮平移 · 双击绘图区恢复自适应 · Esc 关闭光标";
|
||||
|
||||
/** @brief 标签页右键菜单命令 ID。 */
|
||||
const int kMenuTabNew = 1;
|
||||
@@ -459,15 +456,14 @@ namespace jdi
|
||||
|
||||
const float tabBarHeight = m_tabBar.DesiredHeight();
|
||||
const float toolbarHeight = m_toolbar.DesiredHeight();
|
||||
const float statusHeight = ScaleOf(kStatusBarHeight, m_scale);
|
||||
const float splitterWidth = ScaleOf(kSplitterWidth, m_scale);
|
||||
|
||||
m_tabBarRect = gd::RectF(0.0f, 0.0f, width, tabBarHeight);
|
||||
m_toolbarRect = gd::RectF(0.0f, m_tabBarRect.Y + m_tabBarRect.Height, width, toolbarHeight);
|
||||
m_statusRect = gd::RectF(0.0f, height - statusHeight, width, statusHeight);
|
||||
|
||||
// 不再保留底部状态栏,内容区一直延伸到窗口底部。
|
||||
const float contentTop = m_toolbarRect.Y + m_toolbarRect.Height;
|
||||
const float contentHeight = std::max(0.0f, m_statusRect.Y - contentTop);
|
||||
const float contentHeight = std::max(0.0f, height - contentTop);
|
||||
|
||||
float panelWidth = ScaleOf(m_panelWidth, m_scale);
|
||||
const float maximum = std::min(ScaleOf(kMaxPanelWidth, m_scale), width * 0.45f);
|
||||
@@ -567,15 +563,6 @@ namespace jdi
|
||||
SynchronizeView(false);
|
||||
|
||||
m_toolbar.SetDocumentLoaded(!m_dataSet.Empty());
|
||||
if (!m_dataSet.Empty())
|
||||
{
|
||||
m_toolbar.SetStatusText(L"视图 " + FormatValue(m_plot.ViewMinTime()) + L" ~ " +
|
||||
FormatValue(m_plot.ViewMaxTime()));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_toolbar.SetStatusText(std::wstring());
|
||||
}
|
||||
|
||||
// 工具栏可切换按钮的选中状态始终与当前标签页配置保持一致。
|
||||
m_toolbar.SetToggleState(ToolbarAction::ZOOM_TIME,
|
||||
@@ -600,8 +587,6 @@ namespace jdi
|
||||
|
||||
UiFillRect(g, m_splitterRect, m_theme.border);
|
||||
|
||||
PaintStatusBar(g);
|
||||
|
||||
// 弹出菜单与浮动拾色器始终绘制在最上层,保证与主界面风格一致。
|
||||
m_menu.Paint(g);
|
||||
m_colorPicker.Paint(g);
|
||||
@@ -615,70 +600,20 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
void Application::PaintStatusBar(gd::Graphics& g)
|
||||
{
|
||||
UiFillRect(g, m_statusRect, m_theme.statusBg);
|
||||
UiDrawLine(g, m_statusRect.X, m_statusRect.Y, m_statusRect.X + m_statusRect.Width, m_statusRect.Y,
|
||||
m_theme.border, 1.0f);
|
||||
|
||||
const float padding = ScaleOf(12.0f, m_scale);
|
||||
const float halfWidth = m_statusRect.Width * 0.5f;
|
||||
|
||||
std::wstring leftText;
|
||||
if (!m_dataSet.Empty())
|
||||
{
|
||||
const TabConfig& tab = ActiveTab();
|
||||
leftText = std::to_wstring(m_dataSet.FileCount()) + L" 个文件 · " +
|
||||
FormatCount(static_cast<unsigned long long>(m_dataSet.SignalCount())) + L" 条信号 · " +
|
||||
std::to_wstring(tab.VisibleCount()) + L" 条已选 · " +
|
||||
std::to_wstring(tab.Lanes().size()) + L" 个网格";
|
||||
}
|
||||
else
|
||||
{
|
||||
leftText = L"未加载数据 · 请点击「打开 CSV」或拖拽文件到窗口";
|
||||
}
|
||||
|
||||
UiText(g, UiEllipsize(g, leftText, m_fonts.uiSmall, halfWidth - padding * 2.0f), m_fonts.uiSmall,
|
||||
gd::RectF(m_statusRect.X + padding, m_statusRect.Y, halfWidth - padding, m_statusRect.Height),
|
||||
m_theme.text, TextAlign::LEFT);
|
||||
|
||||
std::wstring rightText = kStatusHint;
|
||||
std::uint32_t rightColor = m_theme.textMuted;
|
||||
if (!m_statusMessage.empty())
|
||||
{
|
||||
rightText = m_statusMessage;
|
||||
rightColor = m_statusIsError ? m_theme.danger : m_theme.textStrong;
|
||||
}
|
||||
else if (m_plot.CursorEnabled() && !m_dataSet.Empty())
|
||||
{
|
||||
rightText = L"光标 · " + FormatValue(m_plot.CursorTime());
|
||||
rightColor = m_theme.text;
|
||||
}
|
||||
|
||||
UiText(g, UiEllipsize(g, rightText, m_fonts.uiSmall, halfWidth - padding * 2.0f), m_fonts.uiSmall,
|
||||
gd::RectF(m_statusRect.X + halfWidth, m_statusRect.Y, halfWidth - padding, m_statusRect.Height),
|
||||
rightColor, TextAlign::RIGHT);
|
||||
}
|
||||
|
||||
void Application::SetStatusMessage(const std::wstring& text, bool isError)
|
||||
{
|
||||
m_statusMessage = text;
|
||||
m_statusIsError = isError;
|
||||
// 界面不再占用底部空间显示常驻提示;仅把真实错误以对话框形式反馈给用户。
|
||||
if (!isError || text.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
MessageBoxW(m_hwnd, text.c_str(), kWindowTitle, MB_ICONWARNING | MB_OK);
|
||||
}
|
||||
|
||||
void Application::UpdateWindowTitle()
|
||||
{
|
||||
std::wstring title = kWindowTitle;
|
||||
if (m_activeTab >= 0 && m_activeTab < static_cast<int>(m_tabs.size()))
|
||||
{
|
||||
title += L" — ";
|
||||
title += m_tabs[static_cast<std::size_t>(m_activeTab)].Name();
|
||||
}
|
||||
if (!m_dataSet.Empty())
|
||||
{
|
||||
title += L"(" + std::to_wstring(m_dataSet.FileCount()) + L" 个文件)";
|
||||
}
|
||||
SetWindowTextW(m_hwnd, title.c_str());
|
||||
// 标题栏只显示应用名称,不附带标签页、文件数量等无关信息。
|
||||
SetWindowTextW(m_hwnd, kWindowTitle);
|
||||
}
|
||||
|
||||
void Application::OpenFileDialog()
|
||||
@@ -1071,6 +1006,58 @@ namespace jdi
|
||||
m_menu.Open(MenuScope::TAB_BAR, bounds, anchor.x, anchor.y, items);
|
||||
}
|
||||
|
||||
void Application::ShowPlotContextMenu(int laneIndex, int x, int y)
|
||||
{
|
||||
if (laneIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_plotMenuLane = laneIndex;
|
||||
|
||||
std::vector<MenuItem> items;
|
||||
|
||||
MenuItem header;
|
||||
header.kind = MenuItemKind::HEADER;
|
||||
header.label = L"网格 " + std::to_wstring(laneIndex + 1);
|
||||
items.push_back(header);
|
||||
|
||||
const bool isAuto = m_plot.LaneRangeIsAuto(laneIndex);
|
||||
|
||||
MenuItem autoItem;
|
||||
autoItem.label = isAuto ? L"Y 轴自适应(当前)" : L"恢复 Y 轴自适应";
|
||||
autoItem.id = PLOT_CMD_LANE_AUTO;
|
||||
autoItem.checked = isAuto;
|
||||
autoItem.enabled = !isAuto;
|
||||
items.push_back(autoItem);
|
||||
|
||||
MenuItem maxItem;
|
||||
maxItem.label = L"设置 Y 轴最大值…";
|
||||
maxItem.id = PLOT_CMD_LANE_Y_MAX;
|
||||
items.push_back(maxItem);
|
||||
|
||||
MenuItem minItem;
|
||||
minItem.label = L"设置 Y 轴最小值…";
|
||||
minItem.id = PLOT_CMD_LANE_Y_MIN;
|
||||
items.push_back(minItem);
|
||||
|
||||
MenuItem separator;
|
||||
separator.kind = MenuItemKind::SEPARATOR;
|
||||
items.push_back(separator);
|
||||
|
||||
MenuItem fitItem;
|
||||
fitItem.label = L"全部自适应";
|
||||
fitItem.id = PLOT_CMD_FIT_ALL;
|
||||
items.push_back(fitItem);
|
||||
|
||||
RECT client = {};
|
||||
GetClientRect(m_hwnd, &client);
|
||||
const gd::RectF bounds(0.0f, 0.0f, static_cast<float>(client.right - client.left),
|
||||
static_cast<float>(client.bottom - client.top));
|
||||
|
||||
m_menu.Open(MenuScope::PLOT_VIEW, bounds, x, y, items);
|
||||
}
|
||||
|
||||
void Application::DispatchMenuResult(const MenuResult& result)
|
||||
{
|
||||
if (!result.valid)
|
||||
@@ -1090,6 +1077,31 @@ namespace jdi
|
||||
InvalidateRect(m_hwnd, nullptr, FALSE);
|
||||
break;
|
||||
}
|
||||
case MenuScope::PLOT_VIEW:
|
||||
{
|
||||
const int laneIndex = m_plotMenuLane;
|
||||
m_plotMenuLane = -1;
|
||||
switch (result.id)
|
||||
{
|
||||
case PLOT_CMD_LANE_AUTO:
|
||||
m_plot.ResetLaneRange(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_Y_MAX:
|
||||
m_plot.BeginLaneValueEdit(laneIndex, true);
|
||||
break;
|
||||
case PLOT_CMD_LANE_Y_MIN:
|
||||
m_plot.BeginLaneValueEdit(laneIndex, false);
|
||||
break;
|
||||
case PLOT_CMD_FIT_ALL:
|
||||
m_plot.FitAll();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
SetFocus(m_hwnd);
|
||||
InvalidateRect(m_hwnd, nullptr, TRUE);
|
||||
break;
|
||||
}
|
||||
case MenuScope::TAB_BAR:
|
||||
{
|
||||
const int index = m_tabMenuIndex;
|
||||
@@ -1367,8 +1379,8 @@ namespace jdi
|
||||
const int x = GET_X_LPARAM(lParam);
|
||||
const int y = GET_Y_LPARAM(lParam);
|
||||
|
||||
// 时间光标处于精确输入态时,点击绘图区之外视为提交(与线宽输入一致)。
|
||||
if (m_plot.IsEditingCursor() && !m_plot.HitTest(x, y))
|
||||
// 内联数值输入处于活动态时,点击绘图区之外视为提交(与线宽输入一致)。
|
||||
if (m_plot.IsEditing() && !m_plot.HitTest(x, y))
|
||||
{
|
||||
m_plot.OnEditKey(VK_RETURN);
|
||||
}
|
||||
@@ -1533,6 +1545,18 @@ namespace jdi
|
||||
m_activeTarget = Target::NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 绘图区右键:弹出该网格的 Y 轴设置菜单(含上下限输入)。
|
||||
if (m_plot.HitTest(x, y))
|
||||
{
|
||||
const int laneIndex = m_plot.LaneIndexAt(x, y);
|
||||
if (laneIndex >= 0)
|
||||
{
|
||||
ShowPlotContextMenu(laneIndex, x, y);
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_RBUTTONUP:
|
||||
@@ -1600,7 +1624,7 @@ namespace jdi
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (m_plot.IsEditingCursor())
|
||||
if (m_plot.IsEditing())
|
||||
{
|
||||
if (m_plot.OnEditKey(wParam))
|
||||
{
|
||||
@@ -1625,7 +1649,7 @@ namespace jdi
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (m_plot.IsEditingCursor())
|
||||
if (m_plot.IsEditing())
|
||||
{
|
||||
if (m_plot.OnEditChar(static_cast<wchar_t>(wParam)))
|
||||
{
|
||||
|
||||
@@ -21,15 +21,38 @@ namespace jdi
|
||||
enum class PlotDragMode : int
|
||||
{
|
||||
NONE = 0, /**< 未拖拽。 */
|
||||
PAN_TIME, /**< 水平平移时间轴(常规模式下的左键拖拽)。 */
|
||||
PAN, /**< 常规模式:水平平移时间轴 + 纵向偏移当前网格幅值。 */
|
||||
RUBBER_TIME, /**< 时间轴缩放选区(竖向贯穿全高)。 */
|
||||
RUBBER_VALUE, /**< Y 轴缩放选区(限定在某个网格内)。 */
|
||||
SCROLL_LANES, /**< 拖动网格滚动条。 */
|
||||
CURSOR_MOVE /**< 拖拽时间光标。 */
|
||||
CURSOR_MOVE, /**< 拖拽时间光标。 */
|
||||
MOVE_SIGNAL /**< 拖拽信号曲线到其它网格。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 波形绘图区:网格、时间轴、光标、缩放与自适应。
|
||||
* @brief 绘图区内的内联数值输入目标。
|
||||
*/
|
||||
enum class PlotEditTarget : int
|
||||
{
|
||||
NONE = 0, /**< 未处于输入状态。 */
|
||||
CURSOR_TIME, /**< 编辑时间光标位置。 */
|
||||
LANE_Y_MAX, /**< 编辑某网格的 Y 轴最大值。 */
|
||||
LANE_Y_MIN /**< 编辑某网格的 Y 轴最小值。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 绘图区右键菜单命令标识(与信号面板的命令空间相互独立)。
|
||||
*/
|
||||
enum PlotMenuCommand : int
|
||||
{
|
||||
PLOT_CMD_LANE_AUTO = 1, /**< 该网格恢复 Y 轴自适应。 */
|
||||
PLOT_CMD_LANE_Y_MAX = 2, /**< 输入该网格 Y 轴最大值。 */
|
||||
PLOT_CMD_LANE_Y_MIN = 3, /**< 输入该网格 Y 轴最小值。 */
|
||||
PLOT_CMD_FIT_ALL = 4 /**< 全部网格与时间轴自适应。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 波形绘图区:网格、时间轴、光标、缩放、自适应与信号拖拽。
|
||||
*
|
||||
* 绘图区同时依赖数据层(DataSet,只读)与当前标签页配置(TabConfig,可写),
|
||||
* 视图范围、网格滚动与时间光标均保存在标签页配置中,因此各标签页互不干扰。
|
||||
@@ -201,10 +224,40 @@ namespace jdi
|
||||
PlotZoomMode CurrentZoomMode() const;
|
||||
|
||||
/**
|
||||
* @brief 判断是否已进入精确时间输入状态。
|
||||
* @brief 查询坐标命中的网格下标(用于右键菜单)。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @return 网格下标,未命中返回 -1。
|
||||
*/
|
||||
int LaneIndexAt(int x, int y) const;
|
||||
|
||||
/**
|
||||
* @brief 判断某网格的 Y 轴是否处于自适应状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 自适应返回 true。
|
||||
*/
|
||||
bool LaneRangeIsAuto(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 让某网格的 Y 轴恢复自适应。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool ResetLaneRange(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 进入某网格 Y 轴上限/下限的内联输入状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] editMax 为 true 编辑上限,为 false 编辑下限。
|
||||
* @return 进入成功返回 true。
|
||||
*/
|
||||
bool BeginLaneValueEdit(int laneIndex, bool editMax);
|
||||
|
||||
/**
|
||||
* @brief 判断是否处于任意内联数值输入状态。
|
||||
* @return 处于编辑态返回 true。
|
||||
*/
|
||||
bool IsEditingCursor() const { return m_cursorEditing; }
|
||||
bool IsEditing() const { return m_editTarget != PlotEditTarget::NONE; }
|
||||
|
||||
/**
|
||||
* @brief 处理编辑态下的字符输入。
|
||||
@@ -373,6 +426,21 @@ namespace jdi
|
||||
*/
|
||||
bool ApplyValueSelection(int laneIndex, float y0, float y1);
|
||||
|
||||
/**
|
||||
* @brief 以指定倍率缩放某网格的幅值轴(保持锚点幅值不动)。
|
||||
* @param [IN] laneIndex 目标网格下标。
|
||||
* @param [IN] factor 缩放倍率,大于 1 表示放大。
|
||||
* @param [IN] anchorValue 锚点幅值。
|
||||
*/
|
||||
void ScaleLaneRange(int laneIndex, double factor, double anchorValue);
|
||||
|
||||
/**
|
||||
* @brief 平移某网格的幅值轴(偏移波形显示位置)。
|
||||
* @param [IN] laneIndex 目标网格下标。
|
||||
* @param [IN] deltaValue 幅值偏移量。
|
||||
*/
|
||||
void ShiftLaneRange(int laneIndex, double deltaValue);
|
||||
|
||||
/**
|
||||
* @brief 计算时间光标当前的横坐标。
|
||||
* @return 横坐标。
|
||||
@@ -393,17 +461,34 @@ namespace jdi
|
||||
bool IsOnCursorLine(int x) const;
|
||||
|
||||
/**
|
||||
* @brief 进入时间光标的精确时间输入状态。
|
||||
* @return 进入成功返回 true。
|
||||
* @brief 精确检测鼠标位置是否命中某条可见信号曲线。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @return 命中的信号引用,未命中返回无效引用。
|
||||
*/
|
||||
bool BeginCursorEdit();
|
||||
SignalRef HitTestSignal(int x, int y) const;
|
||||
|
||||
/**
|
||||
* @brief 结束时间光标的精确时间输入。
|
||||
* @brief 计算当前内联输入框的矩形(依编辑目标而定)。
|
||||
* @return 输入框矩形,未处于编辑态时返回空矩形。
|
||||
*/
|
||||
gd::RectF EditFieldRect() const;
|
||||
|
||||
/**
|
||||
* @brief 以给定目标与初始文本进入内联输入状态。
|
||||
* @param [IN] target 编辑目标。
|
||||
* @param [IN] laneIndex 目标网格下标(光标编辑时忽略)。
|
||||
* @param [IN] initialText 初始文本。
|
||||
* @return 进入成功返回 true。
|
||||
*/
|
||||
bool BeginEdit(PlotEditTarget target, int laneIndex, const std::wstring& initialText);
|
||||
|
||||
/**
|
||||
* @brief 结束内联输入。
|
||||
* @param [IN] commit 是否提交当前输入。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool EndCursorEdit(bool commit);
|
||||
bool EndEdit(bool commit);
|
||||
|
||||
/**
|
||||
* @brief 构建单条信号在当前视图下的折线点序列。
|
||||
@@ -421,10 +506,9 @@ namespace jdi
|
||||
* @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<double>& timeTicks, double timeStep);
|
||||
const std::vector<double>& timeTicks);
|
||||
|
||||
/**
|
||||
* @brief 绘制底部时间轴。
|
||||
@@ -447,6 +531,18 @@ namespace jdi
|
||||
*/
|
||||
void PaintRubberBand(gd::Graphics& g);
|
||||
|
||||
/**
|
||||
* @brief 绘制内联数值输入框(时间光标或网格 Y 轴上下限)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
*/
|
||||
void PaintEditField(gd::Graphics& g);
|
||||
|
||||
/**
|
||||
* @brief 绘制信号悬停提示与拖拽虚影。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
*/
|
||||
void PaintSignalInteraction(gd::Graphics& g);
|
||||
|
||||
/**
|
||||
* @brief 绘制网格纵向滚动条。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
@@ -503,10 +599,9 @@ namespace jdi
|
||||
gd::RectF m_timeAxisRect; /**< 时间轴区域。 */
|
||||
gd::RectF m_scrollBarRect; /**< 网格滚动条区域。 */
|
||||
|
||||
float m_yAxisWidth = 74.0f; /**< 纵轴标签列宽度(逻辑像素)。 */
|
||||
float m_labelBandWidth = 66.0f; /**< 网格内侧纵轴标尺带宽度(逻辑像素)。 */
|
||||
float m_timeAxisHeight = 32.0f; /**< 时间轴高度(逻辑像素)。 */
|
||||
float m_laneGap = 6.0f; /**< 网格间距(逻辑像素)。 */
|
||||
float m_minLaneHeight = 46.0f; /**< 网格最小高度(逻辑像素)。 */
|
||||
float m_minLaneHeight = 54.0f; /**< 网格最小高度(逻辑像素)。 */
|
||||
float m_laneContentHeight = 0.0f; /**< 网格内容总高度。 */
|
||||
bool m_scrollBarVisible = false; /**< 是否需要显示滚动条。 */
|
||||
|
||||
@@ -517,15 +612,21 @@ namespace jdi
|
||||
int m_dragStartY = 0; /**< 拖拽起点纵坐标。 */
|
||||
int m_dragLastX = 0; /**< 上一次拖拽横坐标。 */
|
||||
int m_dragLastY = 0; /**< 上一次拖拽纵坐标。 */
|
||||
gd::RectF m_rubberRect; /**< 拖拽选区矩形。 */
|
||||
gd::RectF m_rubberRect; /**< 拖拽选区矩形。 */
|
||||
|
||||
SignalRef m_hoverSignal; /**< 鼠标悬停命中的信号(曲线高亮)。 */
|
||||
SignalRef m_dragSignal; /**< 正在被拖拽的信号。 */
|
||||
int m_dragTargetLane = -1; /**< 拖拽信号时的目标网格下标。 */
|
||||
|
||||
bool m_cursorHover = false; /**< 鼠标是否悬停在时间光标上。 */
|
||||
bool m_cursorEditing = false; /**< 是否处于精确时间输入状态。 */
|
||||
bool m_cursorEditFresh = false; /**< 编辑框是否处于“首次输入整体替换”状态。 */
|
||||
std::wstring m_cursorEditText; /**< 编辑框当前文本。 */
|
||||
gd::RectF m_cursorEditRect; /**< 编辑框矩形(用于命中测试与绘制)。 */
|
||||
PlotEditTarget m_editTarget = PlotEditTarget::NONE; /**< 当前内联输入目标。 */
|
||||
bool m_editFresh = false; /**< 输入框是否处于“首次输入整体替换”状态。 */
|
||||
std::wstring m_editText; /**< 输入框当前文本。 */
|
||||
int m_editLane = -1; /**< 内联输入所作用的网格下标。 */
|
||||
|
||||
int m_hoverLane = -1; /**< 当前悬停网格下标。 */
|
||||
int m_mouseX = 0; /**< 最近一次鼠标横坐标(用于悬停提示定位)。 */
|
||||
int m_mouseY = 0; /**< 最近一次鼠标纵坐标(用于悬停提示定位)。 */
|
||||
std::vector<LaneVisual> m_lanes; /**< 当前网格绘制信息集合。 */
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -58,12 +58,6 @@ namespace jdi
|
||||
*/
|
||||
void SetDocumentLoaded(bool hasDocument);
|
||||
|
||||
/**
|
||||
* @brief 设置工具栏右侧的状态文本。
|
||||
* @param [IN] text 状态文本。
|
||||
*/
|
||||
void SetStatusText(const std::wstring& text);
|
||||
|
||||
/**
|
||||
* @brief 设置可切换按钮的选中状态。
|
||||
* @param [IN] action 目标按钮动作。
|
||||
@@ -156,7 +150,6 @@ namespace jdi
|
||||
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
||||
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
||||
bool m_documentLoaded = false; /**< 是否已加载数据。 */
|
||||
std::wstring m_statusText; /**< 右侧状态文本。 */
|
||||
std::vector<Button> m_buttons; /**< 按钮集合。 */
|
||||
gd::RectF m_area; /**< 工具栏整体区域。 */
|
||||
int m_hoverIndex = -1; /**< 当前悬停按钮下标。 */
|
||||
|
||||
@@ -36,11 +36,6 @@ namespace jdi
|
||||
m_documentLoaded = hasDocument;
|
||||
}
|
||||
|
||||
void Toolbar::SetStatusText(const std::wstring& text)
|
||||
{
|
||||
m_statusText = text;
|
||||
}
|
||||
|
||||
float Toolbar::DesiredHeight() const
|
||||
{
|
||||
return (kButtonHeight + kTopPadding * 2.0f) * m_scale;
|
||||
@@ -71,7 +66,7 @@ namespace jdi
|
||||
};
|
||||
|
||||
const Spec specs[] = {
|
||||
{ToolbarAction::OPEN_FILE, L"打开 CSV", true, false, false},
|
||||
{ToolbarAction::OPEN_FILE, L"导入文件", true, false, false},
|
||||
{ToolbarAction::FIT_ALL, L"适应视图", false, true, false},
|
||||
{ToolbarAction::ZOOM_TIME, L"时间轴缩放", false, true, true},
|
||||
{ToolbarAction::ZOOM_VALUE, L"Y 轴缩放", false, true, true},
|
||||
@@ -81,13 +76,17 @@ namespace jdi
|
||||
{ToolbarAction::SELECT_ALL, L"全选信号", false, true, false},
|
||||
{ToolbarAction::CLEAR_ALL, L"清空勾选", false, true, false},
|
||||
};
|
||||
const std::size_t specCount = sizeof(specs) / sizeof(specs[0]);
|
||||
|
||||
// 按钮宽度按实际文本像素宽 + 固定内边距计算,避免中文标签被裁切。
|
||||
float cursorX = area.X + kSidePadding * m_scale;
|
||||
const float top = area.Y + kTopPadding * m_scale;
|
||||
const float height = kButtonHeight * m_scale;
|
||||
const float horizontalPadding = kButtonPadding * 2.0f * m_scale;
|
||||
|
||||
// 先建立全部按钮并测量宽度,再决定“导入文件”固定在左、其余按钮整体靠右。
|
||||
m_buttons.clear();
|
||||
m_buttons.reserve(specCount);
|
||||
|
||||
for (const Spec& spec : specs)
|
||||
{
|
||||
Button button;
|
||||
@@ -97,17 +96,31 @@ namespace jdi
|
||||
button.needsDocument = spec.needsDocument;
|
||||
button.toggle = spec.toggle;
|
||||
button.width = horizontalPadding + UiMeasureTextWidth(m_fonts->ui, button.label);
|
||||
button.rect = gd::RectF(cursorX, top, button.width, height);
|
||||
cursorX += button.width + kButtonGap * m_scale;
|
||||
|
||||
if (spec.action == ToolbarAction::OPEN_FILE || spec.action == ToolbarAction::CURSOR ||
|
||||
spec.action == ToolbarAction::SPLIT_LANES)
|
||||
{
|
||||
cursorX += kGroupGap * m_scale;
|
||||
}
|
||||
|
||||
m_buttons.push_back(std::move(button));
|
||||
}
|
||||
|
||||
// 首按钮(导入文件)靠左;其余按钮作为整体靠右,并留出一组额外间距。
|
||||
const float leftX = area.X + kSidePadding * m_scale;
|
||||
float groupWidth = 0.0f;
|
||||
for (std::size_t index = 1; index < m_buttons.size(); ++index)
|
||||
{
|
||||
groupWidth += m_buttons[index].width;
|
||||
if (index + 1 < m_buttons.size())
|
||||
{
|
||||
groupWidth += kButtonGap * m_scale;
|
||||
}
|
||||
}
|
||||
|
||||
float cursorX = leftX;
|
||||
m_buttons[0].rect = gd::RectF(cursorX, top, m_buttons[0].width, height);
|
||||
|
||||
const float rightEdge = area.X + area.Width - kSidePadding * m_scale;
|
||||
cursorX = std::max(leftX + m_buttons[0].width + kGroupGap * m_scale, rightEdge - groupWidth);
|
||||
for (std::size_t index = 1; index < m_buttons.size(); ++index)
|
||||
{
|
||||
m_buttons[index].rect = gd::RectF(cursorX, top, m_buttons[index].width, height);
|
||||
cursorX += m_buttons[index].width + kButtonGap * m_scale;
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::SetToggleState(ToolbarAction action, bool checked)
|
||||
@@ -195,14 +208,6 @@ namespace jdi
|
||||
UiText(g, button.label, m_fonts->ui, button.rect, textColor, TextAlign::CENTER);
|
||||
}
|
||||
|
||||
// 右侧状态文本。
|
||||
if (!m_statusText.empty())
|
||||
{
|
||||
const float rightPadding = kSidePadding * m_scale;
|
||||
gd::RectF textRect(m_area.X, m_area.Y, m_area.Width - rightPadding, m_area.Height);
|
||||
UiText(g, m_statusText, m_fonts->uiSmall, textRect, m_theme->textMuted, TextAlign::RIGHT);
|
||||
}
|
||||
|
||||
// 底部分隔线。
|
||||
UiDrawLine(g, m_area.X, m_area.Y + m_area.Height - 1.0f, m_area.X + m_area.Width,
|
||||
m_area.Y + m_area.Height - 1.0f, m_theme->border, 1.0f);
|
||||
|
||||
@@ -124,20 +124,6 @@ namespace jdi
|
||||
*/
|
||||
void UiDrawLine(gd::Graphics& g, float x1, float y1, float x2, float y2, std::uint32_t argb, float width);
|
||||
|
||||
/**
|
||||
* @brief 绘制一条虚线。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] x1 起点横坐标。
|
||||
* @param [IN] y1 起点纵坐标。
|
||||
* @param [IN] x2 终点横坐标。
|
||||
* @param [IN] y2 终点纵坐标。
|
||||
* @param [IN] argb 线条颜色。
|
||||
* @param [IN] width 线条宽度。
|
||||
* @param [IN] dashPattern 虚线样式。
|
||||
*/
|
||||
void UiDrawDashedLine(gd::Graphics& g, float x1, float y1, float x2, float y2, std::uint32_t argb,
|
||||
float width, gd::DashStyle dashPattern = gd::DashStyleDash);
|
||||
|
||||
/**
|
||||
* @brief 在指定矩形内绘制单行文本(垂直居中,超长自动省略)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace jdi
|
||||
{
|
||||
NONE = 0, /**< 无来源。 */
|
||||
SIGNAL_PANEL = 1, /**< 信号面板。 */
|
||||
TAB_BAR = 2 /**< 标签栏。 */
|
||||
TAB_BAR = 2, /**< 标签栏。 */
|
||||
PLOT_VIEW = 3 /**< 波形绘图区。 */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,16 +166,6 @@ namespace jdi
|
||||
g.DrawLine(&pen, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void UiDrawDashedLine(gd::Graphics& g, float x1, float y1, float x2, float y2, std::uint32_t argb,
|
||||
float width, gd::DashStyle dashPattern)
|
||||
{
|
||||
gd::Pen pen(ToGdiColor(argb), width);
|
||||
pen.SetDashStyle(dashPattern);
|
||||
pen.SetStartCap(gd::LineCapFlat);
|
||||
pen.SetEndCap(gd::LineCapFlat);
|
||||
g.DrawLine(&pen, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void UiText(gd::Graphics& g, const std::wstring& text, HFONT font, const gd::RectF& rect, std::uint32_t argb,
|
||||
TextAlign align)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user