fix: 修复网格徽标菜单与标签页编号,重构样式区并新增透明度与浮动拾色器
信号列表 - 网格徽标(G1/G2)改为在左键抬起时弹出菜单,修复此前「按住才显示、松手即消失」的问题。 - 文件分组行新增关闭按钮(✕),可直接从工作区移除文件,与右键菜单「关闭该文件」等价。 - 信号名改为完整保留文件内的列名原文,不再剥离 "[n]" 序号前缀与括号内容。 标签页 - 新建标签页改用「当前未被占用的最小编号」,关闭标签页后编号可被复用, 修复「开 3 关 2 再新建会变成视图 4」的问题。 样式编辑区(统一为固定四行网格布局,面板缩放时不再错位) - 移除顶部「样式 · 信号名 · 文件名」标题行,选中信号即为编辑对象。 - 绘制方式取消「阶梯」,标签由「方式」改为「样式」,仅保留线段 / 散点 / 点线。 - 线宽仅保留最细与最粗两个预设,中间改为带左右箭头的数值旋钮,单位为 px; 支持单击或双击数值框直接键入(首次键入替换原值,回车提交、Esc 取消、越界钳制到 0.5-6.0)。 - 颜色预设后新增与预设色块完全同尺寸、同外框样式的「加号」小方格, 点击后弹出浮动拾色面板(与 G1/G2 菜单同风格的浮动窗口),支持 RGB 三通道滑块、 实时预览与十六进制显示;点击面板外部即收起,取色实时应用到当前信号。 - 移除样式区末尾的网格选择控件,该功能统一由信号行的 G 徽标承载。 - 新增透明度滑块,默认 100%,可在 0-100% 之间调节,并实时反映到波形、 图例色块与信号行色块的显示效果。 其他 - 信号面板默认宽度与行内间距微调,使较长的列名可以完整显示。 - 失焦时提交线宽输入并收起浮动拾色器与弹出菜单。
This commit is contained in:
1 parent
eb868b2679
commit
729494d1ec
9 files changed
+1248
-465
No files matched your search
@@ -8,6 +8,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ColorPickerPopup.hpp"
|
||||||
#include "DataModel.hpp"
|
#include "DataModel.hpp"
|
||||||
#include "PaintUtil.hpp"
|
#include "PaintUtil.hpp"
|
||||||
#include "PlotView.hpp"
|
#include "PlotView.hpp"
|
||||||
@@ -146,6 +147,12 @@ namespace jdi
|
|||||||
*/
|
*/
|
||||||
void NewTab();
|
void NewTab();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 生成当前未被占用的最小标签页名称(关闭标签页后编号可被复用)。
|
||||||
|
* @return 形如「视图 N」的名称。
|
||||||
|
*/
|
||||||
|
std::wstring NextTabName() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭指定标签页。
|
* @brief 关闭指定标签页。
|
||||||
* @param [IN] index 标签页下标。
|
* @param [IN] index 标签页下标。
|
||||||
@@ -257,10 +264,12 @@ namespace jdi
|
|||||||
bool m_gdiplusReady = false; /**< GDI+ 是否初始化成功。 */
|
bool m_gdiplusReady = false; /**< GDI+ 是否初始化成功。 */
|
||||||
|
|
||||||
Theme m_theme; /**< 主题配色。 */
|
Theme m_theme; /**< 主题配色。 */
|
||||||
UiFonts m_fonts; /**< 界面字体。 */ PopupMenu m_menu; /**< 与主题一致的自绘弹出菜单。 */ DataSet m_dataSet; /**< 全局共享的数据集合。 */
|
UiFonts m_fonts; /**< 界面字体。 */
|
||||||
|
PopupMenu m_menu; /**< 与主题一致的自绘弹出菜单。 */
|
||||||
|
ColorPickerPopup m_colorPicker; /**< 与主题一致的浮动拾色器。 */
|
||||||
|
DataSet m_dataSet; /**< 全局共享的数据集合。 */
|
||||||
std::vector<TabConfig> m_tabs; /**< 标签页配置集合。 */
|
std::vector<TabConfig> m_tabs; /**< 标签页配置集合。 */
|
||||||
int m_activeTab = -1; /**< 当前激活标签页下标。 */
|
int m_activeTab = -1; /**< 当前激活标签页下标。 */
|
||||||
int m_tabNameCounter = 0; /**< 标签页命名计数器。 */
|
|
||||||
|
|
||||||
TabBar m_tabBar; /**< 顶部标签栏。 */
|
TabBar m_tabBar; /**< 顶部标签栏。 */
|
||||||
Toolbar m_toolbar; /**< 顶部工具栏。 */
|
Toolbar m_toolbar; /**< 顶部工具栏。 */
|
||||||
@@ -274,7 +283,7 @@ namespace jdi
|
|||||||
gd::RectF m_plotRect; /**< 绘图区区域。 */
|
gd::RectF m_plotRect; /**< 绘图区区域。 */
|
||||||
gd::RectF m_statusRect; /**< 状态栏区域。 */
|
gd::RectF m_statusRect; /**< 状态栏区域。 */
|
||||||
|
|
||||||
float m_panelWidth = 300.0f; /**< 面板宽度(逻辑像素)。 */
|
float m_panelWidth = 366.0f; /**< 面板宽度(逻辑像素)。 */
|
||||||
bool m_draggingSplitter = false; /**< 是否正在拖动分隔条。 */
|
bool m_draggingSplitter = false; /**< 是否正在拖动分隔条。 */
|
||||||
int m_splitterGrabOffset = 0; /**< 分隔条拖动的抓取偏移。 */
|
int m_splitterGrabOffset = 0; /**< 分隔条拖动的抓取偏移。 */
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace jdi
|
|||||||
const float kStatusBarHeight = 26.0f; /**< 状态栏高度(逻辑像素)。 */
|
const float kStatusBarHeight = 26.0f; /**< 状态栏高度(逻辑像素)。 */
|
||||||
const float kSplitterWidth = 4.0f; /**< 分隔条宽度(逻辑像素)。 */
|
const float kSplitterWidth = 4.0f; /**< 分隔条宽度(逻辑像素)。 */
|
||||||
const float kMinPanelWidth = 220.0f; /**< 面板最小宽度(逻辑像素)。 */
|
const float kMinPanelWidth = 220.0f; /**< 面板最小宽度(逻辑像素)。 */
|
||||||
const float kMaxPanelWidth = 480.0f; /**< 面板最大宽度(逻辑像素)。 */
|
const float kMaxPanelWidth = 560.0f; /**< 面板最大宽度(逻辑像素)。 */
|
||||||
const int kMinClientWidth = 860; /**< 窗口最小客户区宽度。 */
|
const int kMinClientWidth = 860; /**< 窗口最小客户区宽度。 */
|
||||||
const int kMinClientHeight = 540; /**< 窗口最小客户区高度。 */
|
const int kMinClientHeight = 540; /**< 窗口最小客户区高度。 */
|
||||||
const UINT kMessageRemoveFile = WM_APP + 1; /**< 面板请求移除文件的自定义消息。 */
|
const UINT kMessageRemoveFile = WM_APP + 1; /**< 面板请求移除文件的自定义消息。 */
|
||||||
@@ -197,7 +197,9 @@ namespace jdi
|
|||||||
m_toolbar.SetScale(m_scale);
|
m_toolbar.SetScale(m_scale);
|
||||||
m_menu.Setup(&m_fonts, &m_theme);
|
m_menu.Setup(&m_fonts, &m_theme);
|
||||||
m_menu.SetScale(m_scale);
|
m_menu.SetScale(m_scale);
|
||||||
m_panel.Setup(m_hwnd, &m_fonts, &m_theme, &m_menu);
|
m_colorPicker.Setup(&m_fonts, &m_theme);
|
||||||
|
m_colorPicker.SetScale(m_scale);
|
||||||
|
m_panel.Setup(m_hwnd, &m_fonts, &m_theme, &m_menu, &m_colorPicker);
|
||||||
m_panel.SetScale(m_scale);
|
m_panel.SetScale(m_scale);
|
||||||
m_plot.Setup(&m_fonts, &m_theme);
|
m_plot.Setup(&m_fonts, &m_theme);
|
||||||
m_plot.SetScale(m_scale);
|
m_plot.SetScale(m_scale);
|
||||||
@@ -242,6 +244,7 @@ namespace jdi
|
|||||||
m_tabBar.SetScale(m_scale);
|
m_tabBar.SetScale(m_scale);
|
||||||
m_toolbar.SetScale(m_scale);
|
m_toolbar.SetScale(m_scale);
|
||||||
m_menu.SetScale(m_scale);
|
m_menu.SetScale(m_scale);
|
||||||
|
m_colorPicker.SetScale(m_scale);
|
||||||
m_panel.SetScale(m_scale);
|
m_panel.SetScale(m_scale);
|
||||||
m_plot.SetScale(m_scale);
|
m_plot.SetScale(m_scale);
|
||||||
ReleaseBackBuffer();
|
ReleaseBackBuffer();
|
||||||
@@ -270,16 +273,37 @@ namespace jdi
|
|||||||
|
|
||||||
void Application::NewTab()
|
void Application::NewTab()
|
||||||
{
|
{
|
||||||
++m_tabNameCounter;
|
|
||||||
|
|
||||||
TabConfig tab;
|
TabConfig tab;
|
||||||
tab.SetName(L"视图 " + std::to_wstring(m_tabNameCounter));
|
tab.SetName(NextTabName());
|
||||||
tab.SyncToDataSet(m_dataSet);
|
tab.SyncToDataSet(m_dataSet);
|
||||||
|
|
||||||
m_tabs.push_back(std::move(tab));
|
m_tabs.push_back(std::move(tab));
|
||||||
SetActiveTab(static_cast<int>(m_tabs.size()) - 1);
|
SetActiveTab(static_cast<int>(m_tabs.size()) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::wstring Application::NextTabName() const
|
||||||
|
{
|
||||||
|
// 取当前未被占用的最小编号,使关闭标签页后编号可以被复用(3 开 2 关再新建回到「视图 2」)。
|
||||||
|
for (int number = 1; number <= static_cast<int>(m_tabs.size()) + 1; ++number)
|
||||||
|
{
|
||||||
|
const std::wstring candidate = L"视图 " + std::to_wstring(number);
|
||||||
|
bool used = false;
|
||||||
|
for (const TabConfig& tab : m_tabs)
|
||||||
|
{
|
||||||
|
if (tab.Name() == candidate)
|
||||||
|
{
|
||||||
|
used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!used)
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return L"视图 " + std::to_wstring(m_tabs.size() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::CloseTab(int index)
|
void Application::CloseTab(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= static_cast<int>(m_tabs.size()))
|
if (index < 0 || index >= static_cast<int>(m_tabs.size()))
|
||||||
@@ -570,8 +594,9 @@ namespace jdi
|
|||||||
|
|
||||||
PaintStatusBar(g);
|
PaintStatusBar(g);
|
||||||
|
|
||||||
// 弹出菜单始终绘制在最上层,保证与主界面风格一致。
|
// 弹出菜单与浮动拾色器始终绘制在最上层,保证与主界面风格一致。
|
||||||
m_menu.Paint(g);
|
m_menu.Paint(g);
|
||||||
|
m_colorPicker.Paint(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
HDC windowDc = GetDC(m_hwnd);
|
HDC windowDc = GetDC(m_hwnd);
|
||||||
@@ -1140,6 +1165,72 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拾色器打开时接管鼠标与键盘(优先级低于弹出菜单、高于常规组件)。
|
||||||
|
if (m_colorPicker.IsOpen())
|
||||||
|
{
|
||||||
|
bool repaint = false;
|
||||||
|
bool consume = false;
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case WM_MOUSEMOVE:
|
||||||
|
repaint = m_colorPicker.OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
// 拾色器接管输入后,取消底层组件可能仍在进行的拖拽。
|
||||||
|
m_panel.OnLButtonUp(0, 0);
|
||||||
|
m_plot.OnLButtonUp(0, 0);
|
||||||
|
m_activeTarget = Target::NONE;
|
||||||
|
m_draggingSplitter = false;
|
||||||
|
m_colorPicker.OnLButtonDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
if (m_colorPicker.IsOpen())
|
||||||
|
{
|
||||||
|
SetCapture(hwnd);
|
||||||
|
}
|
||||||
|
repaint = true;
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
m_colorPicker.OnLButtonUp();
|
||||||
|
ReleaseCapture();
|
||||||
|
repaint = true;
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
m_colorPicker.OnKeyDown(wParam);
|
||||||
|
repaint = true;
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
case WM_MBUTTONDOWN:
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
|
m_colorPicker.Close();
|
||||||
|
repaint = true;
|
||||||
|
consume = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取色变化实时应用到当前选中信号。
|
||||||
|
std::uint32_t pickedColor = 0;
|
||||||
|
if (m_colorPicker.TakeChanged(pickedColor))
|
||||||
|
{
|
||||||
|
m_panel.ApplyColor(pickedColor);
|
||||||
|
repaint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repaint)
|
||||||
|
{
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
if (consume)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
case WM_SIZE:
|
case WM_SIZE:
|
||||||
@@ -1461,12 +1552,41 @@ namespace jdi
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_KEYDOWN:
|
case WM_KEYDOWN:
|
||||||
|
// 线宽数值输入状态优先接管按键(回车提交、Esc 取消、退格删除)。
|
||||||
|
if (m_panel.IsEditingWidth())
|
||||||
|
{
|
||||||
|
if (m_panel.OnEditKey(wParam))
|
||||||
|
{
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (HandleKeyDown(wParam))
|
if (HandleKeyDown(wParam))
|
||||||
{
|
{
|
||||||
InvalidateRect(hwnd, nullptr, TRUE);
|
InvalidateRect(hwnd, nullptr, TRUE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case WM_CHAR:
|
||||||
|
{
|
||||||
|
// 线宽数值仅接受数字与小数点,其余字符一律吞掉。
|
||||||
|
if (m_panel.IsEditingWidth())
|
||||||
|
{
|
||||||
|
if (m_panel.OnEditChar(static_cast<wchar_t>(wParam)))
|
||||||
|
{
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_KILLFOCUS:
|
||||||
|
// 失焦时提交输入并收起临时界面,避免失去鼠标前景后控件仍处于编辑态。
|
||||||
|
m_panel.CloseTransientUi();
|
||||||
|
m_colorPicker.Close();
|
||||||
|
m_menu.Close();
|
||||||
|
InvalidateRect(hwnd, nullptr, FALSE);
|
||||||
|
return 0;
|
||||||
case kMessageRemoveFile:
|
case kMessageRemoveFile:
|
||||||
{
|
{
|
||||||
RemoveFile(static_cast<int>(wParam));
|
RemoveFile(static_cast<int>(wParam));
|
||||||
|
|||||||
@@ -43,23 +43,32 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 组装图例文本:信号名已在括号中携带单位时不再重复追加。
|
* @brief 组装图例文本:直接使用列名原文(已含序号与括号描述)。
|
||||||
* @param [IN] data 信号数据。
|
* @param [IN] data 信号数据。
|
||||||
* @return 图例文本。
|
* @return 图例文本。
|
||||||
*/
|
*/
|
||||||
std::wstring BuildLegendText(const SignalData& data)
|
std::wstring BuildLegendText(const SignalData& data)
|
||||||
{
|
|
||||||
if (data.unit.empty())
|
|
||||||
{
|
{
|
||||||
return data.name;
|
return data.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::wstring bracketed = L"(" + data.unit + L")";
|
/**
|
||||||
if (ContainsInsensitive(data.name, bracketed))
|
* @brief 将信号样式颜色按不透明度换算为实际绘制颜色。
|
||||||
|
* @param [IN] style 信号样式。
|
||||||
|
* @return 叠加不透明度后的 0xAARRGGBB 颜色值。
|
||||||
|
*/
|
||||||
|
std::uint32_t StyleColor(const SignalStyle& style)
|
||||||
{
|
{
|
||||||
return data.name;
|
double opacity = static_cast<double>(style.opacity);
|
||||||
|
if (opacity < 0.0)
|
||||||
|
{
|
||||||
|
opacity = 0.0;
|
||||||
}
|
}
|
||||||
return data.name + L" [" + data.unit + L"]";
|
if (opacity > 1.0)
|
||||||
|
{
|
||||||
|
opacity = 1.0;
|
||||||
|
}
|
||||||
|
return WithAlpha(style.color, static_cast<unsigned int>(opacity * 255.0 + 0.5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,7 +1105,8 @@ namespace jdi
|
|||||||
|
|
||||||
if (drawLine && !breaks.empty())
|
if (drawLine && !breaks.empty())
|
||||||
{
|
{
|
||||||
gd::Pen pen(ToGdiColor(resolved.style->color), resolved.style->lineWidth * m_scale);
|
gd::Pen pen(ToGdiColor(StyleColor(*resolved.style)),
|
||||||
|
resolved.style->lineWidth * m_scale);
|
||||||
pen.SetLineJoin(gd::LineJoinRound);
|
pen.SetLineJoin(gd::LineJoinRound);
|
||||||
pen.SetStartCap(gd::LineCapRound);
|
pen.SetStartCap(gd::LineCapRound);
|
||||||
pen.SetEndCap(gd::LineCapRound);
|
pen.SetEndCap(gd::LineCapRound);
|
||||||
@@ -1121,7 +1131,7 @@ namespace jdi
|
|||||||
m_scale);
|
m_scale);
|
||||||
const std::size_t scatterStride =
|
const std::size_t scatterStride =
|
||||||
std::max<std::size_t>(1, points.size() / kMaxScatterPoints);
|
std::max<std::size_t>(1, points.size() / kMaxScatterPoints);
|
||||||
gd::SolidBrush brush(ToGdiColor(resolved.style->color));
|
gd::SolidBrush brush(ToGdiColor(StyleColor(*resolved.style)));
|
||||||
for (std::size_t k = 0; k < points.size(); k += scatterStride)
|
for (std::size_t k = 0; k < points.size(); k += scatterStride)
|
||||||
{
|
{
|
||||||
g.FillEllipse(&brush, points[k].X - radius, points[k].Y - radius, radius * 2.0f,
|
g.FillEllipse(&brush, points[k].X - radius, points[k].Y - radius, radius * 2.0f,
|
||||||
@@ -1228,7 +1238,7 @@ namespace jdi
|
|||||||
const float swatchSize = 8.0f * m_scale;
|
const float swatchSize = 8.0f * m_scale;
|
||||||
UiFillRoundRect(g,
|
UiFillRoundRect(g,
|
||||||
gd::RectF(cursorX, top + (height - swatchSize) * 0.5f, swatchSize, swatchSize),
|
gd::RectF(cursorX, top + (height - swatchSize) * 0.5f, swatchSize, swatchSize),
|
||||||
1.5f * m_scale, resolved.style->color);
|
1.5f * m_scale, StyleColor(*resolved.style));
|
||||||
cursorX += swatchSize + 5.0f * m_scale;
|
cursorX += swatchSize + 5.0f * m_scale;
|
||||||
|
|
||||||
UiText(g, label, m_fonts->uiSmall, gd::RectF(cursorX, top, textWidth + 2.0f, height),
|
UiText(g, label, m_fonts->uiSmall, gd::RectF(cursorX, top, textWidth + 2.0f, height),
|
||||||
@@ -1425,7 +1435,7 @@ namespace jdi
|
|||||||
|
|
||||||
const float radius = 3.4f * m_scale;
|
const float radius = 3.4f * m_scale;
|
||||||
gd::SolidBrush ring(ToGdiColor(m_theme->cardBg));
|
gd::SolidBrush ring(ToGdiColor(m_theme->cardBg));
|
||||||
gd::SolidBrush fill(ToGdiColor(resolved.style->color));
|
gd::SolidBrush fill(ToGdiColor(StyleColor(*resolved.style)));
|
||||||
g.FillEllipse(&ring, x - radius - 1.2f * m_scale, y - radius - 1.2f * m_scale,
|
g.FillEllipse(&ring, x - radius - 1.2f * m_scale, y - radius - 1.2f * m_scale,
|
||||||
(radius + 1.2f * m_scale) * 2.0f, (radius + 1.2f * m_scale) * 2.0f);
|
(radius + 1.2f * m_scale) * 2.0f, (radius + 1.2f * m_scale) * 2.0f);
|
||||||
g.FillEllipse(&fill, x - radius, y - radius, radius * 2.0f, radius * 2.0f);
|
g.FillEllipse(&fill, x - radius, y - radius, radius * 2.0f, radius * 2.0f);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ColorPickerPopup.hpp"
|
||||||
#include "DataModel.hpp"
|
#include "DataModel.hpp"
|
||||||
#include "PaintUtil.hpp"
|
#include "PaintUtil.hpp"
|
||||||
#include "PopupMenu.hpp"
|
#include "PopupMenu.hpp"
|
||||||
@@ -40,8 +41,8 @@ namespace jdi
|
|||||||
* @brief 左侧信号面板:按文件分组的信号树、可见性勾选与样式编辑。
|
* @brief 左侧信号面板:按文件分组的信号树、可见性勾选与样式编辑。
|
||||||
*
|
*
|
||||||
* 面板只读取数据层(DataSet),全部显示配置读写均落在当前标签页(TabConfig)上,
|
* 面板只读取数据层(DataSet),全部显示配置读写均落在当前标签页(TabConfig)上,
|
||||||
* 因此不同标签页的信号勾选与样式互不影响。所有弹出菜单均使用主题化自绘菜单,
|
* 因此不同标签页的信号勾选与样式互不影响。所有弹出菜单与拾色器均使用主题化自绘
|
||||||
* 不出现系统默认菜单样式。
|
* 浮动面板,不出现系统默认菜单样式。
|
||||||
*/
|
*/
|
||||||
class SignalPanel
|
class SignalPanel
|
||||||
{
|
{
|
||||||
@@ -49,13 +50,15 @@ namespace jdi
|
|||||||
SignalPanel();
|
SignalPanel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绑定宿主窗口、字体、主题与共用弹出菜单。
|
* @brief 绑定宿主窗口、字体、主题与共用浮动控件。
|
||||||
* @param [IN] host 宿主窗口句柄。
|
* @param [IN] host 宿主窗口句柄。
|
||||||
* @param [IN] fonts 字体集合,需在面板生命周期内保持有效。
|
* @param [IN] fonts 字体集合,需在面板生命周期内保持有效。
|
||||||
* @param [IN] theme 主题配色,需在面板生命周期内保持有效。
|
* @param [IN] theme 主题配色,需在面板生命周期内保持有效。
|
||||||
* @param [IN] menu 共用的主题化弹出菜单,需在面板生命周期内保持有效。
|
* @param [IN] menu 共用的主题化弹出菜单,需在面板生命周期内保持有效。
|
||||||
|
* @param [IN] colorPicker 共用的浮动拾色器,需在面板生命周期内保持有效。
|
||||||
*/
|
*/
|
||||||
void Setup(HWND host, const UiFonts* fonts, const Theme* theme, PopupMenu* menu);
|
void Setup(HWND host, const UiFonts* fonts, const Theme* theme, PopupMenu* menu,
|
||||||
|
ColorPickerPopup* colorPicker);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绑定数据层与当前标签页配置。
|
* @brief 绑定数据层与当前标签页配置。
|
||||||
@@ -65,7 +68,7 @@ namespace jdi
|
|||||||
void SetContext(const DataSet* dataSet, TabConfig* tab);
|
void SetContext(const DataSet* dataSet, TabConfig* tab);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置宿主客户区矩形(用于约束弹出菜单位置)。
|
* @brief 设置宿主客户区矩形(用于约束浮动面板位置)。
|
||||||
* @param [IN] bounds 客户区矩形。
|
* @param [IN] bounds 客户区矩形。
|
||||||
*/
|
*/
|
||||||
void SetClientBounds(const gd::RectF& bounds) { m_clientBounds = bounds; }
|
void SetClientBounds(const gd::RectF& bounds) { m_clientBounds = bounds; }
|
||||||
@@ -73,19 +76,13 @@ namespace jdi
|
|||||||
/**
|
/**
|
||||||
* @brief 标记列表结构失效(数据或折叠状态变化后调用)。
|
* @brief 标记列表结构失效(数据或折叠状态变化后调用)。
|
||||||
*/
|
*/
|
||||||
void InvalidateRows()
|
void InvalidateRows() { m_rowsDirty = true; }
|
||||||
{
|
|
||||||
m_rowsDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 判断列表内容是否需要纵向滚动。
|
* @brief 判断列表内容是否需要纵向滚动。
|
||||||
* @return 内容超出可视区域返回 true。
|
* @return 内容超出可视区域返回 true。
|
||||||
*/
|
*/
|
||||||
bool NeedsScrollBar() const
|
bool NeedsScrollBar() const { return m_contentHeight > m_listRect.Height + 0.5f; }
|
||||||
{
|
|
||||||
return m_contentHeight > m_listRect.Height + 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 DPI 缩放比例。
|
* @brief 设置 DPI 缩放比例。
|
||||||
@@ -122,7 +119,7 @@ namespace jdi
|
|||||||
bool OnLButtonDown(int x, int y);
|
bool OnLButtonDown(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 处理鼠标左键抬起。
|
* @brief 处理鼠标左键抬起(网格徽标与自定义颜色入口在此刻触发展开)。
|
||||||
* @param [IN] x 客户区横坐标。
|
* @param [IN] x 客户区横坐标。
|
||||||
* @param [IN] y 客户区纵坐标。
|
* @param [IN] y 客户区纵坐标。
|
||||||
* @return 视觉状态发生变化返回 true。
|
* @return 视觉状态发生变化返回 true。
|
||||||
@@ -130,7 +127,7 @@ namespace jdi
|
|||||||
bool OnLButtonUp(int x, int y);
|
bool OnLButtonUp(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 处理鼠标左键双击(切换信号可见性)。
|
* @brief 处理鼠标左键双击(线宽数值进入输入状态,其余行切换可见性)。
|
||||||
* @param [IN] x 客户区横坐标。
|
* @param [IN] x 客户区横坐标。
|
||||||
* @param [IN] y 客户区纵坐标。
|
* @param [IN] y 客户区纵坐标。
|
||||||
* @return 需要重绘返回 true。
|
* @return 需要重绘返回 true。
|
||||||
@@ -166,7 +163,34 @@ namespace jdi
|
|||||||
bool OnMenuCommand(const MenuResult& result);
|
bool OnMenuCommand(const MenuResult& result);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭自定义颜色面板等临时展开的界面。
|
* @brief 判断线宽数值框是否处于输入状态。
|
||||||
|
* @return 正在输入返回 true。
|
||||||
|
*/
|
||||||
|
bool IsEditingWidth() const { return m_widthEditing; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向线宽输入框送入一个字符。
|
||||||
|
* @param [IN] character 输入字符('\r' 提交、'\b' 退格)。
|
||||||
|
* @return 界面状态发生变化返回 true。
|
||||||
|
*/
|
||||||
|
bool OnEditChar(wchar_t character);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向线宽输入框送入一个按键。
|
||||||
|
* @param [IN] key 虚拟键码。
|
||||||
|
* @return 界面状态发生变化返回 true。
|
||||||
|
*/
|
||||||
|
bool OnEditKey(WPARAM key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将浮动拾色器的取色结果应用到当前选中信号。
|
||||||
|
* @param [IN] color 0xAARRGGBB 颜色值。
|
||||||
|
* @return 应用成功返回 true。
|
||||||
|
*/
|
||||||
|
bool ApplyColor(std::uint32_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭自定义颜色面板与线宽输入状态。
|
||||||
* @return 界面状态发生变化返回 true。
|
* @return 界面状态发生变化返回 true。
|
||||||
*/
|
*/
|
||||||
bool CloseTransientUi();
|
bool CloseTransientUi();
|
||||||
@@ -215,18 +239,19 @@ namespace jdi
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 样式编辑区中的可点击条目类型。
|
* @brief 样式编辑区中的可交互条目类型。
|
||||||
*/
|
*/
|
||||||
enum class StyleHit : int
|
enum class StyleHit : int
|
||||||
{
|
{
|
||||||
NONE = 0, /**< 未命中。 */
|
NONE = 0, /**< 未命中。 */
|
||||||
COLOR, /**< 预设颜色色块。 */
|
COLOR, /**< 预设颜色色块。 */
|
||||||
CUSTOM_CHIP, /**< 自定义颜色入口。 */
|
CUSTOM_CHIP, /**< 自定义颜色入口。 */
|
||||||
WIDTH_PRESET, /**< 线宽预设按钮。 */
|
WIDTH_PRESET, /**< 线宽预设按钮(细/粗)。 */
|
||||||
WIDTH_MINUS, /**< 线宽减号。 */
|
WIDTH_MINUS, /**< 线宽减号。 */
|
||||||
WIDTH_PLUS, /**< 线宽加号。 */
|
WIDTH_PLUS, /**< 线宽加号。 */
|
||||||
|
WIDTH_VALUE, /**< 线宽数值框。 */
|
||||||
MODE, /**< 绘制样式按钮。 */
|
MODE, /**< 绘制样式按钮。 */
|
||||||
SLIDER, /**< 自定义颜色滑块。 */
|
OPACITY, /**< 不透明度滑块。 */
|
||||||
TOGGLE_ALL /**< 全部显示/隐藏按钮。 */
|
TOGGLE_ALL /**< 全部显示/隐藏按钮。 */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -312,18 +337,11 @@ namespace jdi
|
|||||||
void PaintSignalRow(gd::Graphics& g, int row);
|
void PaintSignalRow(gd::Graphics& g, int row);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制样式编辑区。
|
* @brief 绘制样式编辑区(固定四行:颜色 / 线宽 / 样式 / 透明度)。
|
||||||
* @param [IN,OUT] g 绘图上下文。
|
* @param [IN,OUT] g 绘图上下文。
|
||||||
*/
|
*/
|
||||||
void PaintStyleSection(gd::Graphics& g);
|
void PaintStyleSection(gd::Graphics& g);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制自定义颜色面板。
|
|
||||||
* @param [IN,OUT] g 绘图上下文。
|
|
||||||
* @param [IN] style 当前编辑的信号样式。
|
|
||||||
*/
|
|
||||||
void PaintColorPicker(gd::Graphics& g, const SignalStyle& style);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制列表滚动条。
|
* @brief 绘制列表滚动条。
|
||||||
* @param [IN,OUT] g 绘图上下文。
|
* @param [IN,OUT] g 绘图上下文。
|
||||||
@@ -342,7 +360,7 @@ namespace jdi
|
|||||||
void LayoutStyleControls();
|
void LayoutStyleControls();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 取得当前样式编辑区所需高度。
|
* @brief 取得样式编辑区固定所需高度。
|
||||||
* @return 逻辑高度(未乘缩放比例)。
|
* @return 逻辑高度(未乘缩放比例)。
|
||||||
*/
|
*/
|
||||||
float DesiredStyleHeight() const;
|
float DesiredStyleHeight() const;
|
||||||
@@ -371,6 +389,13 @@ namespace jdi
|
|||||||
*/
|
*/
|
||||||
void OpenLaneMenu(const SignalRef& ref, int x, int y);
|
void OpenLaneMenu(const SignalRef& ref, int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打开浮动拾色器(由自定义颜色入口触发)。
|
||||||
|
* @param [IN] style 当前信号样式。
|
||||||
|
* @return 打开成功返回 true。
|
||||||
|
*/
|
||||||
|
bool OpenColorPickerPopup(const SignalStyle& style);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 判断当前标签页中全部信号是否均为可见状态。
|
* @brief 判断当前标签页中全部信号是否均为可见状态。
|
||||||
* @return 全部可见且至少有一条信号返回 true。
|
* @return 全部可见且至少有一条信号返回 true。
|
||||||
@@ -391,16 +416,30 @@ namespace jdi
|
|||||||
bool StepLineWidth(float delta);
|
bool StepLineWidth(float delta);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 展开自定义颜色面板并同步初始取色。
|
* @brief 进入线宽数值输入状态。
|
||||||
* @param [IN] style 当前信号样式。
|
* @return 状态发生变化返回 true。
|
||||||
* @return 界面状态发生变化返回 true。
|
|
||||||
*/
|
*/
|
||||||
bool OpenColorPicker(const SignalStyle& style);
|
bool BeginWidthEdit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束线宽数值输入状态。
|
||||||
|
* @param [IN] commit 是否提交输入值。
|
||||||
|
* @return 状态或取值发生变化返回 true。
|
||||||
|
*/
|
||||||
|
bool EndWidthEdit(bool commit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调整当前信号不透明度。
|
||||||
|
* @param [IN] x 客户区横坐标。
|
||||||
|
* @return 取值发生变化返回 true。
|
||||||
|
*/
|
||||||
|
bool ApplyOpacityFromX(float x);
|
||||||
|
|
||||||
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
|
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
|
||||||
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
||||||
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
||||||
PopupMenu* m_menu = nullptr; /**< 共用弹出菜单。 */
|
PopupMenu* m_menu = nullptr; /**< 共用弹出菜单。 */
|
||||||
|
ColorPickerPopup* m_colorPicker = nullptr; /**< 共用浮动拾色器。 */
|
||||||
const DataSet* m_dataSet = nullptr; /**< 数据集合。 */
|
const DataSet* m_dataSet = nullptr; /**< 数据集合。 */
|
||||||
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
|
TabConfig* m_tab = nullptr; /**< 当前标签页配置。 */
|
||||||
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
||||||
@@ -415,7 +454,8 @@ namespace jdi
|
|||||||
|
|
||||||
std::array<gd::RectF, 12> m_colorRects; /**< 预设颜色色块矩形。 */
|
std::array<gd::RectF, 12> m_colorRects; /**< 预设颜色色块矩形。 */
|
||||||
int m_colorCount = 0; /**< 实际可用的预设颜色数量。 */
|
int m_colorCount = 0; /**< 实际可用的预设颜色数量。 */
|
||||||
gd::RectF m_customChipRect; /**< 自定义颜色入口矩形。 */
|
gd::RectF m_customChipRect; /**< 自定义颜色入口矩形(与预设色块同尺寸)。 */
|
||||||
|
float m_swatchSize = 18.0f; /**< 颜色色块边长(逻辑像素)。 */
|
||||||
|
|
||||||
gd::RectF m_widthThinRect; /**< 最细线宽预设矩形。 */
|
gd::RectF m_widthThinRect; /**< 最细线宽预设矩形。 */
|
||||||
gd::RectF m_widthThickRect; /**< 最粗线宽预设矩形。 */
|
gd::RectF m_widthThickRect; /**< 最粗线宽预设矩形。 */
|
||||||
@@ -426,10 +466,8 @@ namespace jdi
|
|||||||
|
|
||||||
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
|
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
|
||||||
|
|
||||||
gd::RectF m_pickerRect; /**< 自定义颜色面板整体矩形。 */
|
gd::RectF m_opacityTrackRect; /**< 不透明度滑块轨道矩形。 */
|
||||||
std::array<gd::RectF, 3> m_sliderRects; /**< RGB 滑块轨道矩形。 */
|
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
|
||||||
gd::RectF m_pickerPreviewRect; /**< 自定义颜色预览矩形。 */
|
|
||||||
std::array<double, 3> m_pickerChannels = {0.0, 0.0, 0.0}; /**< 自定义颜色的 RGB 分量(0-1)。 */
|
|
||||||
|
|
||||||
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
|
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
|
||||||
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
|
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
|
||||||
@@ -440,13 +478,20 @@ namespace jdi
|
|||||||
float m_scrollOffset = 0.0f; /**< 列表滚动偏移量。 */
|
float m_scrollOffset = 0.0f; /**< 列表滚动偏移量。 */
|
||||||
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
|
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
|
||||||
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
|
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
|
||||||
|
bool m_draggingOpacity = false; /**< 是否正在拖拽不透明度滑块。 */
|
||||||
int m_hoverRow = -1; /**< 当前悬停行。 */
|
int m_hoverRow = -1; /**< 当前悬停行。 */
|
||||||
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
|
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
|
||||||
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
|
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
|
||||||
int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */
|
int m_hoverStyleIndex = -1; /**< 当前悬停的样式条目下标。 */
|
||||||
|
|
||||||
bool m_colorPickerOpen = false; /**< 自定义颜色面板是否展开。 */
|
bool m_widthEditing = false; /**< 线宽数值是否处于输入状态。 */
|
||||||
int m_draggingSlider = -1; /**< 正在拖拽的滑块下标,-1 表示未拖拽。 */
|
bool m_widthEditFresh = false; /**< 输入缓冲是否仍为初始值(首次键入应整体替换)。 */
|
||||||
|
std::wstring m_widthEditText; /**< 线宽输入缓冲。 */
|
||||||
|
|
||||||
|
bool m_pendingLaneMenu = false; /**< 左键抬起时是否弹出网格归属菜单。 */
|
||||||
|
SignalRef m_pendingLaneRef; /**< 待弹出菜单的目标信号。 */
|
||||||
|
bool m_pendingColorPicker = false; /**< 左键抬起时是否弹出拾色器。 */
|
||||||
|
|
||||||
bool m_layoutDirty = false; /**< 是否需要重新布局。 */
|
bool m_layoutDirty = false; /**< 是否需要重新布局。 */
|
||||||
|
|
||||||
SignalRef m_menuTargetSignal; /**< 菜单操作的目标信号。 */
|
SignalRef m_menuTargetSignal; /**< 菜单操作的目标信号。 */
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,161 @@
|
|||||||
|
#ifndef __COLOR_PICKER_POPUP_HPP__
|
||||||
|
#define __COLOR_PICKER_POPUP_HPP__
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <objidl.h>
|
||||||
|
#include <gdiplus.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "PaintUtil.hpp"
|
||||||
|
#include "Theme.hpp"
|
||||||
|
|
||||||
|
namespace jdi
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 浮动式 RGB 自定义颜色拾取器。
|
||||||
|
*
|
||||||
|
* 与弹出菜单一致,不创建独立窗口,而是作为宿主绘制的最后一步直接画到后备缓冲区,
|
||||||
|
* 以浮动面板的形式出现在触发控件附近;取值变化实时生效,点击面板外部即收起。
|
||||||
|
*/
|
||||||
|
class ColorPickerPopup
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ColorPickerPopup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定字体与主题资源。
|
||||||
|
* @param [IN] fonts 字体集合,需在弹出面板生命周期内保持有效。
|
||||||
|
* @param [IN] theme 主题配色,需在弹出面板生命周期内保持有效。
|
||||||
|
*/
|
||||||
|
void Setup(const UiFonts* fonts, const Theme* theme);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 DPI 缩放比例。
|
||||||
|
* @param [IN] scale 缩放比例(1.0 对应 96 DPI)。
|
||||||
|
*/
|
||||||
|
void SetScale(float scale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在指定位置打开拾色面板。
|
||||||
|
* @param [IN] bounds 宿主客户区矩形,用于把面板限制在窗口内。
|
||||||
|
* @param [IN] x 锚点横坐标(客户区坐标)。
|
||||||
|
* @param [IN] y 锚点纵坐标(客户区坐标)。
|
||||||
|
* @param [IN] color 初始颜色(0xAARRGGBB,仅取 RGB 分量)。
|
||||||
|
*/
|
||||||
|
void Open(const gd::RectF& bounds, int x, int y, std::uint32_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭拾色面板。
|
||||||
|
*/
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断拾色面板是否处于打开状态。
|
||||||
|
* @return 打开返回 true。
|
||||||
|
*/
|
||||||
|
bool IsOpen() const { return m_open; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前取色结果。
|
||||||
|
* @return 0xAARRGGBB 颜色值(Alpha 固定为不透明)。
|
||||||
|
*/
|
||||||
|
std::uint32_t CurrentColor() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 取出并清除“取值已变化”标记。
|
||||||
|
* @param [OUT] color 变化后的颜色。
|
||||||
|
* @return 自上次调用以来取值发生过变化返回 true。
|
||||||
|
*/
|
||||||
|
bool TakeChanged(std::uint32_t& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 处理鼠标左键抬起。
|
||||||
|
* @return 需要重绘返回 true。
|
||||||
|
*/
|
||||||
|
bool OnLButtonUp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理键盘按键(Esc 收起)。
|
||||||
|
* @param [IN] key 虚拟键码。
|
||||||
|
* @return 事件已被消费返回 true。
|
||||||
|
*/
|
||||||
|
bool OnKeyDown(WPARAM key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断坐标是否落在拾色面板内部。
|
||||||
|
* @param [IN] x 客户区横坐标。
|
||||||
|
* @param [IN] y 客户区纵坐标。
|
||||||
|
* @return 落在内部返回 true。
|
||||||
|
*/
|
||||||
|
bool ContainsPoint(int x, int y) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 依据锚点与客户区计算面板矩形与内部控件矩形。
|
||||||
|
* @param [IN] bounds 宿主客户区矩形。
|
||||||
|
* @param [IN] x 锚点横坐标。
|
||||||
|
* @param [IN] y 锚点纵坐标。
|
||||||
|
*/
|
||||||
|
void BuildLayout(const gd::RectF& bounds, int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询坐标命中的滑块下标。
|
||||||
|
* @param [IN] x 客户区横坐标。
|
||||||
|
* @param [IN] y 客户区纵坐标。
|
||||||
|
* @return 滑块下标(0=R,1=G,2=B),未命中返回 -1。
|
||||||
|
*/
|
||||||
|
int SliderAtPoint(int x, int y) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用滑块拖拽位置到对应通道。
|
||||||
|
* @param [IN] index 滑块下标。
|
||||||
|
* @param [IN] x 客户区横坐标。
|
||||||
|
*/
|
||||||
|
void ApplySlider(int index, float x);
|
||||||
|
|
||||||
|
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
|
||||||
|
const Theme* m_theme = nullptr; /**< 主题资源。 */
|
||||||
|
float m_scale = 1.0f; /**< DPI 缩放比例。 */
|
||||||
|
|
||||||
|
bool m_open = false; /**< 是否处于打开状态。 */
|
||||||
|
bool m_changed = false; /**< 取值是否发生变化。 */
|
||||||
|
int m_draggingSlider = -1; /**< 正在拖拽的滑块下标,-1 表示未拖拽。 */
|
||||||
|
int m_hoverSlider = -1; /**< 当前悬停的滑块下标。 */
|
||||||
|
std::array<double, 3> m_channels = {0.0, 0.0, 0.0}; /**< RGB 分量(0-1)。 */
|
||||||
|
|
||||||
|
gd::RectF m_rect; /**< 面板整体矩形。 */
|
||||||
|
gd::RectF m_titleRect; /**< 标题区域。 */
|
||||||
|
std::array<gd::RectF, 3> m_tracks; /**< 滑块轨道矩形。 */
|
||||||
|
std::array<gd::RectF, 3> m_values; /**< 数值文本矩形。 */
|
||||||
|
gd::RectF m_previewRect; /**< 预览色块矩形。 */
|
||||||
|
gd::RectF m_hexRect; /**< 十六进制文本矩形。 */
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace jdi
|
||||||
|
|
||||||
|
#endif // __COLOR_PICKER_POPUP_HPP__
|
||||||
@@ -195,6 +195,7 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
bool visible = false; /**< 是否参与绘制。默认关闭,由用户自行勾选。 */
|
bool visible = false; /**< 是否参与绘制。默认关闭,由用户自行勾选。 */
|
||||||
std::uint32_t color = 0xFF268BD2; /**< 波形颜色,格式 0xAARRGGBB。 */
|
std::uint32_t color = 0xFF268BD2; /**< 波形颜色,格式 0xAARRGGBB。 */
|
||||||
|
float opacity = 1.0f; /**< 不透明度,取值 0.0-1.0,默认 1.0。 */
|
||||||
float lineWidth = 1.6f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
|
float lineWidth = 1.6f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
|
||||||
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
|
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,347 @@
|
|||||||
|
#include "ColorPickerPopup.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace jdi
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const float kPanelWidth = 214.0f; /**< 面板宽度(逻辑像素)。 */
|
||||||
|
const float kPadH = 12.0f; /**< 面板左右内边距。 */
|
||||||
|
const float kPadV = 10.0f; /**< 面板上下内边距。 */
|
||||||
|
const float kTitleHeight = 20.0f; /**< 标题行高度。 */
|
||||||
|
const float kTitleGap = 8.0f; /**< 标题与滑块区间距。 */
|
||||||
|
const float kRowHeight = 26.0f; /**< 单个滑块行高度。 */
|
||||||
|
const float kRowGap = 2.0f; /**< 滑块行间距。 */
|
||||||
|
const float kPreviewGap = 10.0f; /**< 滑块区与预览区间距。 */
|
||||||
|
const float kPreviewHeight = 24.0f; /**< 预览行高度。 */
|
||||||
|
const float kLabelWidth = 16.0f; /**< 通道标签宽度。 */
|
||||||
|
const float kLabelGap = 6.0f; /**< 标签与滑块间距。 */
|
||||||
|
const float kValueWidth = 40.0f; /**< 数值文本宽度。 */
|
||||||
|
const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
|
||||||
|
const float kCorner = 6.0f; /**< 面板圆角半径。 */
|
||||||
|
|
||||||
|
/** @brief 通道显示名称。 */
|
||||||
|
const wchar_t* kChannelNames[3] = {L"R", L"G", L"B"};
|
||||||
|
|
||||||
|
/** @brief 通道滑块填充色。 */
|
||||||
|
const std::uint32_t kChannelColors[3] = {0xFFDC322Fu, 0xFF859900u, 0xFF268BD2u};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制水平滑块(轨道 + 已填充部分 + 圆钮)。
|
||||||
|
* @param [IN,OUT] g 绘图上下文。
|
||||||
|
* @param [IN] track 轨道矩形。
|
||||||
|
* @param [IN] ratio 当前值比例(0-1)。
|
||||||
|
* @param [IN] fillColor 已填充部分颜色。
|
||||||
|
* @param [IN] hovered 是否处于悬停或拖拽状态。
|
||||||
|
* @param [IN] scale 缩放比例。
|
||||||
|
*/
|
||||||
|
void DrawChannelSlider(gd::Graphics& g, const gd::RectF& track, double ratio, std::uint32_t fillColor,
|
||||||
|
bool hovered, float scale)
|
||||||
|
{
|
||||||
|
const float radius = track.Height * 0.5f;
|
||||||
|
UiFillRoundRect(g, track, radius, 0x2A657B83u);
|
||||||
|
|
||||||
|
ratio = std::max(0.0, std::min(1.0, ratio));
|
||||||
|
const float fillWidth = std::max(track.Height, static_cast<float>(ratio) * track.Width);
|
||||||
|
UiFillRoundRect(g, gd::RectF(track.X, track.Y, fillWidth, track.Height), radius, fillColor);
|
||||||
|
|
||||||
|
const float knobX = track.X + static_cast<float>(ratio) * track.Width;
|
||||||
|
const float knobRadius = radius + (hovered ? 2.5f * scale : 0.8f * scale);
|
||||||
|
|
||||||
|
gd::SolidBrush knob(ToGdiColor(0xFFFFFFFFu));
|
||||||
|
gd::Pen knobPen(ToGdiColor(0xFF586E75u), 1.4f * scale);
|
||||||
|
g.FillEllipse(&knob, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
|
||||||
|
knobRadius * 2.0f);
|
||||||
|
g.DrawEllipse(&knobPen, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
|
||||||
|
knobRadius * 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 0-1 分量换算为 0-255 整数。
|
||||||
|
* @param [IN] value 分量值。
|
||||||
|
* @return 整数分量。
|
||||||
|
*/
|
||||||
|
unsigned int ToByte(double value)
|
||||||
|
{
|
||||||
|
const double scaled = value * 255.0 + 0.5;
|
||||||
|
if (scaled < 0.0)
|
||||||
|
{
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
if (scaled > 255.0)
|
||||||
|
{
|
||||||
|
return 255u;
|
||||||
|
}
|
||||||
|
return static_cast<unsigned int>(scaled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPickerPopup::ColorPickerPopup() = default;
|
||||||
|
|
||||||
|
void ColorPickerPopup::Setup(const UiFonts* fonts, const Theme* theme)
|
||||||
|
{
|
||||||
|
m_fonts = fonts;
|
||||||
|
m_theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::SetScale(float scale)
|
||||||
|
{
|
||||||
|
m_scale = (scale > 0.1f) ? scale : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t ColorPickerPopup::CurrentColor() const
|
||||||
|
{
|
||||||
|
const unsigned int r = ToByte(m_channels[0]);
|
||||||
|
const unsigned int g = ToByte(m_channels[1]);
|
||||||
|
const unsigned int b = ToByte(m_channels[2]);
|
||||||
|
return 0xFF000000u | (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::TakeChanged(std::uint32_t& color)
|
||||||
|
{
|
||||||
|
if (!m_changed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_changed = false;
|
||||||
|
color = CurrentColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, int x, int y)
|
||||||
|
{
|
||||||
|
const float width = kPanelWidth * m_scale;
|
||||||
|
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + kRowHeight * 3.0f + kRowGap * 2.0f +
|
||||||
|
kPreviewGap + kPreviewHeight) *
|
||||||
|
m_scale;
|
||||||
|
|
||||||
|
float left = static_cast<float>(x);
|
||||||
|
float top = static_cast<float>(y);
|
||||||
|
|
||||||
|
// 优先向右下展开;越界时翻转,最后做边界收敛。
|
||||||
|
if (left + width > bounds.X + bounds.Width)
|
||||||
|
{
|
||||||
|
left = static_cast<float>(x) - width;
|
||||||
|
}
|
||||||
|
if (top + height > bounds.Y + bounds.Height)
|
||||||
|
{
|
||||||
|
top = static_cast<float>(y) - height;
|
||||||
|
}
|
||||||
|
left = std::max(bounds.X + 2.0f, std::min(left, bounds.X + bounds.Width - width - 2.0f));
|
||||||
|
top = std::max(bounds.Y + 2.0f, std::min(top, bounds.Y + bounds.Height - height - 2.0f));
|
||||||
|
|
||||||
|
m_rect = gd::RectF(left, top, width, height);
|
||||||
|
|
||||||
|
m_titleRect = gd::RectF(left + kPadH * m_scale, top + kPadV * m_scale,
|
||||||
|
width - kPadH * 2.0f * m_scale, kTitleHeight * m_scale);
|
||||||
|
|
||||||
|
const float trackLeft = left + (kPadH + kLabelWidth + kLabelGap) * m_scale;
|
||||||
|
const float trackWidth =
|
||||||
|
std::max(40.0f, width - (trackLeft - left) - (kPadH + kValueWidth) * m_scale);
|
||||||
|
const float firstTop = m_titleRect.Y + m_titleRect.Height + kTitleGap * m_scale;
|
||||||
|
|
||||||
|
for (int index = 0; index < 3; ++index)
|
||||||
|
{
|
||||||
|
const float rowTop = firstTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
||||||
|
m_tracks[static_cast<std::size_t>(index)] =
|
||||||
|
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
||||||
|
trackWidth, kTrackHeight * m_scale);
|
||||||
|
m_values[static_cast<std::size_t>(index)] =
|
||||||
|
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop,
|
||||||
|
kValueWidth * m_scale - 6.0f * m_scale, kRowHeight * m_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float previewTop = firstTop + 3.0f * (kRowHeight + kRowGap) * m_scale + kPreviewGap * m_scale;
|
||||||
|
m_previewRect = gd::RectF(trackLeft, previewTop + 2.0f * m_scale, 52.0f * m_scale, 20.0f * m_scale);
|
||||||
|
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + 8.0f * m_scale, previewTop + 2.0f * m_scale,
|
||||||
|
96.0f * m_scale, 20.0f * m_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::Open(const gd::RectF& bounds, int x, int y, std::uint32_t color)
|
||||||
|
{
|
||||||
|
m_channels[0] = static_cast<double>((color >> 16) & 0xFFu) / 255.0;
|
||||||
|
m_channels[1] = static_cast<double>((color >> 8) & 0xFFu) / 255.0;
|
||||||
|
m_channels[2] = static_cast<double>(color & 0xFFu) / 255.0;
|
||||||
|
|
||||||
|
m_open = true;
|
||||||
|
m_changed = false;
|
||||||
|
m_draggingSlider = -1;
|
||||||
|
m_hoverSlider = -1;
|
||||||
|
|
||||||
|
BuildLayout(bounds, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::Close()
|
||||||
|
{
|
||||||
|
m_open = false;
|
||||||
|
m_draggingSlider = -1;
|
||||||
|
m_hoverSlider = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::ContainsPoint(int x, int y) const
|
||||||
|
{
|
||||||
|
if (!m_open)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return UiPointInRect(m_rect, static_cast<float>(x), static_cast<float>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ColorPickerPopup::SliderAtPoint(int x, int y) const
|
||||||
|
{
|
||||||
|
if (!m_open)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float fx = static_cast<float>(x);
|
||||||
|
const float fy = static_cast<float>(y);
|
||||||
|
for (int index = 0; index < 3; ++index)
|
||||||
|
{
|
||||||
|
const gd::RectF& track = m_tracks[static_cast<std::size_t>(index)];
|
||||||
|
// 命中范围在纵向适当放宽,便于拖拽。
|
||||||
|
const gd::RectF hit(track.X - 4.0f * m_scale, track.Y - 9.0f * m_scale,
|
||||||
|
track.Width + 8.0f * m_scale, track.Height + 18.0f * m_scale);
|
||||||
|
if (UiPointInRect(hit, fx, fy))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::ApplySlider(int index, float x)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= 3)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t slot = static_cast<std::size_t>(index);
|
||||||
|
const double ratio = UiSliderRatioFromX(m_tracks[slot], x);
|
||||||
|
if (std::fabs(ratio - m_channels[slot]) < 1.0e-6)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_channels[slot] = ratio;
|
||||||
|
m_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::OnMouseMove(int x, int y)
|
||||||
|
{
|
||||||
|
if (!m_open)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_draggingSlider >= 0)
|
||||||
|
{
|
||||||
|
ApplySlider(m_draggingSlider, static_cast<float>(x));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int hover = SliderAtPoint(x, y);
|
||||||
|
if (hover == m_hoverSlider)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_hoverSlider = hover;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::OnLButtonDown(int x, int y)
|
||||||
|
{
|
||||||
|
if (!m_open)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContainsPoint(x, y))
|
||||||
|
{
|
||||||
|
// 点击面板外部:收起面板(外部点击不再下传,避免误触其他控件)。
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_draggingSlider = SliderAtPoint(x, y);
|
||||||
|
if (m_draggingSlider >= 0)
|
||||||
|
{
|
||||||
|
ApplySlider(m_draggingSlider, static_cast<float>(x));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::OnLButtonUp()
|
||||||
|
{
|
||||||
|
if (!m_open || m_draggingSlider < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_draggingSlider = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorPickerPopup::OnKeyDown(WPARAM key)
|
||||||
|
{
|
||||||
|
if (!m_open)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (key == VK_ESCAPE)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerPopup::Paint(gd::Graphics& g)
|
||||||
|
{
|
||||||
|
if (!m_open || m_theme == nullptr || m_fonts == nullptr || m_rect.Width <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 投影。
|
||||||
|
UiFillRoundRect(g, gd::RectF(m_rect.X + 1.0f * m_scale, m_rect.Y + 2.0f * m_scale, m_rect.Width,
|
||||||
|
m_rect.Height),
|
||||||
|
kCorner * m_scale, 0x14000000u);
|
||||||
|
UiFillRoundRect(g, gd::RectF(m_rect.X + 2.0f * m_scale, m_rect.Y + 4.0f * m_scale, m_rect.Width,
|
||||||
|
m_rect.Height),
|
||||||
|
kCorner * m_scale, 0x10000000u);
|
||||||
|
|
||||||
|
UiFillRoundRect(g, m_rect, kCorner * m_scale, m_theme->cardBg);
|
||||||
|
UiDrawRoundRect(g, m_rect, kCorner * m_scale, m_theme->borderStrong, 1.0f);
|
||||||
|
|
||||||
|
UiText(g, L"自定义颜色", m_fonts->uiSmallBold, m_titleRect, m_theme->textMuted, TextAlign::LEFT);
|
||||||
|
|
||||||
|
for (int index = 0; index < 3; ++index)
|
||||||
|
{
|
||||||
|
const std::size_t slot = static_cast<std::size_t>(index);
|
||||||
|
const gd::RectF& track = m_tracks[slot];
|
||||||
|
|
||||||
|
const gd::RectF labelRect(m_rect.X + kPadH * m_scale, track.Y - 9.0f * m_scale,
|
||||||
|
kLabelWidth * m_scale, kRowHeight * m_scale);
|
||||||
|
UiText(g, kChannelNames[index], m_fonts->uiSmall, labelRect, m_theme->textMuted, TextAlign::LEFT);
|
||||||
|
|
||||||
|
const bool active = (m_draggingSlider == index) || (m_hoverSlider == index);
|
||||||
|
DrawChannelSlider(g, track, m_channels[slot], kChannelColors[index], active, m_scale);
|
||||||
|
|
||||||
|
UiText(g, std::to_wstring(ToByte(m_channels[slot])), m_fonts->monoSmall, m_values[slot],
|
||||||
|
m_theme->text, TextAlign::RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览与十六进制值。
|
||||||
|
const std::uint32_t color = CurrentColor();
|
||||||
|
UiFillRoundRect(g, m_previewRect, 3.0f * m_scale, color);
|
||||||
|
UiDrawRoundRect(g, m_previewRect, 3.0f * m_scale, m_theme->borderStrong, 1.0f);
|
||||||
|
|
||||||
|
wchar_t buffer[16];
|
||||||
|
swprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), L"#%02X%02X%02X", (color >> 16) & 0xFFu,
|
||||||
|
(color >> 8) & 0xFFu, color & 0xFFu);
|
||||||
|
UiText(g, std::wstring(buffer), m_fonts->monoSmall, m_hexRect, m_theme->text, TextAlign::LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace jdi
|
||||||
@@ -37,44 +37,9 @@ namespace jdi
|
|||||||
return ContainsInsensitive(text, L"time") || ContainsInsensitive(text, L"时间");
|
return ContainsInsensitive(text, L"time") || ContainsInsensitive(text, L"时间");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 剥离列名中的 "[n]" 序号前缀,得到显示名称。
|
|
||||||
* @param [IN] header 原始列名。
|
|
||||||
* @return 显示名称。
|
|
||||||
*/
|
|
||||||
std::wstring MakeDisplayName(const std::wstring& header)
|
|
||||||
{
|
|
||||||
const std::wstring text = Trim(header);
|
|
||||||
if (text.size() >= 3 && text[0] == L'[')
|
|
||||||
{
|
|
||||||
const std::size_t close = text.find(L']');
|
|
||||||
if (close != std::wstring::npos && close > 1 && (close + 1) < text.size())
|
|
||||||
{
|
|
||||||
bool numeric = true;
|
|
||||||
for (std::size_t i = 1; i < close; ++i)
|
|
||||||
{
|
|
||||||
if (!iswdigit(text[i]))
|
|
||||||
{
|
|
||||||
numeric = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numeric)
|
|
||||||
{
|
|
||||||
const std::wstring rest = Trim(text.substr(close + 1));
|
|
||||||
if (!rest.empty())
|
|
||||||
{
|
|
||||||
return rest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text.empty() ? header : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从名称末尾的括号中提取单位(仅接受形如 mA / V / deg/s 的短标记)。
|
* @brief 从名称末尾的括号中提取单位(仅接受形如 mA / V / deg/s 的短标记)。
|
||||||
* @param [IN] text 已剥离 "[n]" 前缀的名称。
|
* @param [IN] text 列名(已去除首尾空白)。
|
||||||
* @return 单位文本,无法识别时返回空字符串。
|
* @return 单位文本,无法识别时返回空字符串。
|
||||||
*/
|
*/
|
||||||
std::wstring ExtractUnit(const std::wstring& text)
|
std::wstring ExtractUnit(const std::wstring& text)
|
||||||
@@ -109,46 +74,6 @@ namespace jdi
|
|||||||
return inner;
|
return inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从名称末尾移除已被识别为单位(或描述)的括号片段。
|
|
||||||
* @param [IN] name 已剥离 "[n]" 前缀的名称。
|
|
||||||
* @return 移除尾部括号后的名称(括号片段非单位时原样返回)。
|
|
||||||
*/
|
|
||||||
std::wstring StripBracketSuffix(const std::wstring& name)
|
|
||||||
{
|
|
||||||
if (name.size() < 3 || name.back() != L')')
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t open = name.rfind(L'(');
|
|
||||||
if (open == std::wstring::npos || open == 0 || open + 2 >= name.size())
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::wstring inner = Trim(name.substr(open + 1, name.size() - open - 2));
|
|
||||||
if (inner.empty() || inner.size() > 10)
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仅当括号内容为短单位标记时才剥离,避免误伤 "(5 Points)" 这类描述。
|
|
||||||
for (const wchar_t c : inner)
|
|
||||||
{
|
|
||||||
const bool allowed = iswalpha(c) || iswdigit(c) || c == L'%' || c == L'/' || c == L'^' ||
|
|
||||||
c == L'-' || c == L'.' || c == L'\u00B0' || c == L'\u00B5' ||
|
|
||||||
c == L'\u03A9' || c == L'\u00B2' || c == L'\u00B3' || c == L'\u00B7';
|
|
||||||
if (!allowed)
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::wstring head = Trim(name.substr(0, open));
|
|
||||||
return head.empty() ? name : head;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 依据全局信号序号从调色板取色,保证跨标签页配色一致。
|
* @brief 依据全局信号序号从调色板取色,保证跨标签页配色一致。
|
||||||
* @param [IN] globalIndex 全局信号序号。
|
* @param [IN] globalIndex 全局信号序号。
|
||||||
@@ -421,10 +346,9 @@ namespace jdi
|
|||||||
SignalData signal;
|
SignalData signal;
|
||||||
signal.rawHeader = table.headers[column];
|
signal.rawHeader = table.headers[column];
|
||||||
{
|
{
|
||||||
// 名称与单位分开保存:名称不再保留尾部单位括号,避免重复显示。
|
// 名称完整保留文件内的列名(含 "[n]" 序号与括号描述),仅用于展示不做改写。
|
||||||
const std::wstring baseName = MakeDisplayName(signal.rawHeader);
|
signal.name = Trim(signal.rawHeader);
|
||||||
signal.unit = ExtractUnit(baseName);
|
signal.unit = ExtractUnit(signal.name);
|
||||||
signal.name = StripBracketSuffix(baseName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signal.values.resize(rowCount);
|
signal.values.resize(rowCount);
|
||||||
|
|||||||
Reference in new issue
Block a user