标题栏内存占用 / 样式区增益与取绝对值·中心反转 / 拾色器排版 / 图例去托底与命中优先级
标题栏: - 应用名称后追加「 | RAM : <值>」:竖线两侧各留两个空格(与分组标题「组X | 组名」的间距风格一致)。 - 数值按 KB/MB/GB 自适应、保留两位小数(复用 FormatByteSize);用 K32GetProcessMemoryInfo 取本进程 工作集(与任务管理器「内存」一致,无需新增链接依赖);1 秒定时器刷新,数值未变时不调用 SetWindowTextW。 信号样式自定义栏: - 线宽与透明度交换上下位置(线宽在上、透明度在下)。 - 新增「系数增益」:输入框(默认 1,只接受数值,范围 ±10000),点击进入编辑、回车/点击别处提交。 - 新增「取绝对值」「中心反转」两个同行的切换按钮。 - 三者作用顺序固定为「系数增益 → 取绝对值 → 中心反转」(中心反转最后结算),实现为 ApplyDrawTransform(style, value),在 BuildPolyline(逐点描边与像素列包络两条路径,含粗粒度分组层)、 派生预览曲线与光标取值读数统一调用;样式属于信号本身(跨视图共享)并进入 SceneContentHash / SceneStyleEntry / 折线缓存键,改动会立即重绘。 - 非保序变换(取绝对值 / 反转 / 负增益)会回退到逐采样点变换、不再使用粗粒度包络层,保证包络精确。 自定义颜色面板: - 把底部的「色号 + 预览色块」移到 RGB 滑块上方;右侧一列(预览行 + 三行滑块)与左侧「色域方块 + 色相条」高度严格相等,两列上下边对齐,面板不再为它单独保留一行。 网格内: - 去掉图例与 GX 编号下方的整块不透明托底(它会把网格线整片擦掉,看起来像一个"可点击控件", 而其右侧空白实际并不响应点击)。 - 图例与 GX 徽标改为优先命中:先于时间光标线 / 光标读数 / 边沿计数横向光标 / 选区放大判定, 因此选区放大模式下也能正常拖拽 GX 标签与点选图例(浮动面板仍然优先,与绘制层序一致)。 验证:离屏 harness(.preflight/uitest,SW_HIDE、不动鼠标键盘屏幕)新增 [AK] 标题栏内存读数(格式与竖线间隙、单位阈值、两位小数、随 256MB 分配增长、定时器刷新); [AL] 样式区行序与几何、增益输入提交、两个切换按钮的真实点击、ApplyDrawTransform 顺序,以及 BuildPolyline 顶点范围与变换后数据范围逐项对齐(默认 / 增益 2 / 取绝对值 / 中心反转 / 负增益+取绝对值+中心反转叠加); [AM] 拾色器两列严格等高(140.00 == 140.00); [AN] 选区放大模式下 GX 拖拽重排 + 图例点选 + 网格线穿过图例带(托底已移除)。 另修正旧用例 [AI](远端已删除"光标跟随视界",改为断言光标时间位置不变、允许滑出视窗)。 合计 530/530 通过;make clean && make all 零警告。
This commit is contained in:
1 parent
20b90848fe
commit
c3343907ec
8 files changed
+420
-105
No files matched your search
@@ -395,6 +395,7 @@ namespace jdi
|
|||||||
Target m_activeTarget = Target::NONE; /**< 当前鼠标作用目标。 */
|
Target m_activeTarget = Target::NONE; /**< 当前鼠标作用目标。 */
|
||||||
bool m_trackingLeave = false; /**< 是否已注册鼠标离开通知。 */
|
bool m_trackingLeave = false; /**< 是否已注册鼠标离开通知。 */
|
||||||
bool m_hintTimerActive = false; /**< 提示浮层淡出定时器是否已启动。 */
|
bool m_hintTimerActive = false; /**< 提示浮层淡出定时器是否已启动。 */
|
||||||
|
std::wstring m_windowTitleText; /**< 上次写入窗口标题的完整文本(用于避免重复设置)。 */
|
||||||
int m_tabMenuIndex = -1; /**< 标签栏右键菜单的目标标签页下标。 */
|
int m_tabMenuIndex = -1; /**< 标签栏右键菜单的目标标签页下标。 */
|
||||||
int m_plotMenuLane = -1; /**< 绘图区右键菜单的目标网格下标。 */
|
int m_plotMenuLane = -1; /**< 绘图区右键菜单的目标网格下标。 */
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <commdlg.h>
|
#include <commdlg.h>
|
||||||
|
#include <psapi.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <windowsx.h>
|
#include <windowsx.h>
|
||||||
|
|
||||||
@@ -27,7 +28,16 @@ namespace jdi
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const wchar_t* kWindowClassName = L"JustDataInspectorMainWindow"; /**< 窗口类名。 */
|
const wchar_t* kWindowClassName = L"JustDataInspectorMainWindow"; /**< 窗口类名。 */
|
||||||
const wchar_t* kWindowTitle = L"Just DataInspector"; /**< 窗口标题(仅显示应用名称)。 */
|
const wchar_t* kWindowTitle = L"Just DataInspector"; /**< 窗口标题中的应用名称部分。 */
|
||||||
|
/**
|
||||||
|
* @brief 应用名称与内存占用之间的分隔竖线。
|
||||||
|
*
|
||||||
|
* 与信号分组标题里「组X | 组名」的分隔方式一致:竖线两侧各留出空隙
|
||||||
|
*(`kGroupSeparatorGap` 是那个分隔线的左右间距,这里用两个空格达到同等观感)。
|
||||||
|
*/
|
||||||
|
const wchar_t* kTitleSeparator = L" | ";
|
||||||
|
/** @brief 内存占用的显示前缀(与具体数值之间保留一个空格)。 */
|
||||||
|
const wchar_t* kRamTitlePrefix = L"RAM : ";
|
||||||
const float kSplitterWidth = 4.0f; /**< 分隔条宽度(逻辑像素)。 */
|
const float kSplitterWidth = 4.0f; /**< 分隔条宽度(逻辑像素)。 */
|
||||||
const float kMinPanelWidth = 260.0f; /**< 面板最小宽度(逻辑像素)。 */
|
const float kMinPanelWidth = 260.0f; /**< 面板最小宽度(逻辑像素)。 */
|
||||||
const float kMaxPanelWidth = 400.0f; /**< 面板最大宽度(逻辑像素)。 */
|
const float kMaxPanelWidth = 400.0f; /**< 面板最大宽度(逻辑像素)。 */
|
||||||
@@ -37,6 +47,29 @@ namespace jdi
|
|||||||
const UINT kMessageRemoveSignal = WM_APP + 2; /**< 面板请求移除单个信号的自定义消息。 */
|
const UINT kMessageRemoveSignal = WM_APP + 2; /**< 面板请求移除单个信号的自定义消息。 */
|
||||||
const UINT_PTR kHintTimerId = 1; /**< 提示浮层淡出用的定时器 ID。 */
|
const UINT_PTR kHintTimerId = 1; /**< 提示浮层淡出用的定时器 ID。 */
|
||||||
const UINT kHintTimerPeriodMs = 33; /**< 提示浮层淡出的重绘间隔(约 30fps)。 */
|
const UINT kHintTimerPeriodMs = 33; /**< 提示浮层淡出的重绘间隔(约 30fps)。 */
|
||||||
|
const UINT_PTR kRamTimerId = 2; /**< 标题栏内存占用刷新的定时器 ID。 */
|
||||||
|
const UINT kRamTimerPeriodMs = 1000; /**< 标题栏内存占用的刷新间隔(1 秒)。 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询本进程当前占用的物理内存(工作集)。
|
||||||
|
*
|
||||||
|
* 与任务管理器里显示的“内存”一致:进程已驻留在物理内存中的字节数。
|
||||||
|
* 使用 `K32GetProcessMemoryInfo`(kernel32 导出)以免为 `psapi` 增加一条链接依赖。
|
||||||
|
*
|
||||||
|
* @return 工作集字节数;查询失败返回 0。
|
||||||
|
*/
|
||||||
|
unsigned long long ProcessWorkingSetBytes()
|
||||||
|
{
|
||||||
|
PROCESS_MEMORY_COUNTERS_EX counters = {};
|
||||||
|
counters.cb = sizeof(counters);
|
||||||
|
if (K32GetProcessMemoryInfo(GetCurrentProcess(),
|
||||||
|
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&counters),
|
||||||
|
sizeof(counters)) == FALSE)
|
||||||
|
{
|
||||||
|
return 0ull;
|
||||||
|
}
|
||||||
|
return static_cast<unsigned long long>(counters.WorkingSetSize);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 只失效绘图区所在的矩形。
|
* @brief 只失效绘图区所在的矩形。
|
||||||
@@ -345,6 +378,8 @@ namespace jdi
|
|||||||
NewTab();
|
NewTab();
|
||||||
|
|
||||||
Relayout();
|
Relayout();
|
||||||
|
// 标题栏内存占用按固定间隔刷新(不使用每帧刷新,避免频繁改动窗口标题)。
|
||||||
|
SetTimer(m_hwnd, kRamTimerId, kRamTimerPeriodMs, nullptr);
|
||||||
ShowWindow(m_hwnd, showCommand);
|
ShowWindow(m_hwnd, showCommand);
|
||||||
UpdateWindow(m_hwnd);
|
UpdateWindow(m_hwnd);
|
||||||
LoadCommandLineFiles();
|
LoadCommandLineFiles();
|
||||||
@@ -862,8 +897,19 @@ namespace jdi
|
|||||||
|
|
||||||
void Application::UpdateWindowTitle()
|
void Application::UpdateWindowTitle()
|
||||||
{
|
{
|
||||||
// 标题栏只显示应用名称,不附带标签页、文件数量等无关信息。
|
// 标题栏 = 应用名称 + 分隔竖线 + 内存占用(单位在 KB/MB/GB 间自适应,保留两位小数)。
|
||||||
SetWindowTextW(m_hwnd, kWindowTitle);
|
// 除内存占用外不再附带标签页、文件数量等无关信息。
|
||||||
|
std::wstring title = kWindowTitle;
|
||||||
|
title += kTitleSeparator;
|
||||||
|
title += kRamTitlePrefix;
|
||||||
|
title += FormatByteSize(ProcessWorkingSetBytes());
|
||||||
|
// 数值未变化时不调用 SetWindowTextW,避免无谓地重绘标题栏。
|
||||||
|
if (title == m_windowTitleText)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_windowTitleText = title;
|
||||||
|
SetWindowTextW(m_hwnd, m_windowTitleText.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::OpenFileDialog()
|
void Application::OpenFileDialog()
|
||||||
@@ -2748,6 +2794,12 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
case WM_TIMER:
|
case WM_TIMER:
|
||||||
{
|
{
|
||||||
|
// 标题栏内存占用:只改窗口标题,不动客户区,因此无需重绘。
|
||||||
|
if (wParam == kRamTimerId)
|
||||||
|
{
|
||||||
|
UpdateWindowTitle();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
// 提示浮层淡出:仅重绘,不再做其它处理;Render 会在淡出结束后停表。
|
// 提示浮层淡出:仅重绘,不再做其它处理;Render 会在淡出结束后停表。
|
||||||
if (wParam == kHintTimerId)
|
if (wParam == kHintTimerId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1838,6 +1838,9 @@ namespace jdi
|
|||||||
float laneHeight = 0.0f; /**< 网格高度。 */
|
float laneHeight = 0.0f; /**< 网格高度。 */
|
||||||
double laneYMin = 0.0; /**< 网格幅值下界。 */
|
double laneYMin = 0.0; /**< 网格幅值下界。 */
|
||||||
double laneYMax = 0.0; /**< 网格幅值上界。 */
|
double laneYMax = 0.0; /**< 网格幅值上界。 */
|
||||||
|
float gain = 1.0f; /**< 绘制前的系数增益。 */
|
||||||
|
bool absolute = false; /**< 绘制前是否取绝对值。 */
|
||||||
|
bool inverted = false; /**< 绘制前是否中心反转。 */
|
||||||
std::vector<gd::PointF> points; /**< 折线顶点。 */
|
std::vector<gd::PointF> points; /**< 折线顶点。 */
|
||||||
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
|
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
|
||||||
|
|
||||||
@@ -2001,12 +2004,17 @@ namespace jdi
|
|||||||
float opacity = 1.0f; /**< 不透明度。 */
|
float opacity = 1.0f; /**< 不透明度。 */
|
||||||
float lineWidth = 1.0f; /**< 线宽。 */
|
float lineWidth = 1.0f; /**< 线宽。 */
|
||||||
int mode = 0; /**< 绘制模式。 */
|
int mode = 0; /**< 绘制模式。 */
|
||||||
|
float gain = 1.0f; /**< 系数增益。 */
|
||||||
|
bool absolute = false; /**< 是否取绝对值。 */
|
||||||
|
bool inverted = false; /**< 是否中心反转。 */
|
||||||
|
|
||||||
/** @brief 相等比较(用于判断"逐信号外观样式是否变化")。 */
|
/** @brief 相等比较(用于判断"逐信号外观样式是否变化")。 */
|
||||||
bool operator==(const SceneStyleEntry& other) const
|
bool operator==(const SceneStyleEntry& other) const
|
||||||
{
|
{
|
||||||
return color == other.color && opacity == other.opacity &&
|
return color == other.color && opacity == other.opacity &&
|
||||||
lineWidth == other.lineWidth && mode == other.mode;
|
lineWidth == other.lineWidth && mode == other.mode &&
|
||||||
|
gain == other.gain && absolute == other.absolute &&
|
||||||
|
inverted == other.inverted;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4654,22 +4654,17 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 靠近时间光标线时优先进入拖动,避免误触发平移或缩放选区。
|
// 图例与 GX 编号徽标叠在波形之上、且直接位于网格顶部——用户明确要求它们**优先响应**:
|
||||||
{
|
// 必须先于「时间光标线 / 光标读数 / 边沿计数横向光标 / 选区放大」判定,否则:
|
||||||
int cursorWhich = 0;
|
// * 光标线恰好经过徽标/图例时,按下会变成拖光标;
|
||||||
if (IsOnCursorLine(x, cursorWhich) && UiPointInRect(m_area, fx, fy))
|
// * 选区放大模式下会变成“开始框选”,用户看到的就是“GX 拖不动、图例点不中”。
|
||||||
{
|
// 层序上仍然让位于浮动面板(面板绘制在徽标与图例之上),因此先挡一次面板命中。
|
||||||
m_dragMode = PlotDragMode::CURSOR_MOVE;
|
int guardLane = -1;
|
||||||
m_hoverCursor = cursorWhich;
|
int guardKind = 0;
|
||||||
// 点中的光标成为“活动光标”:它触发的内容将画在上层。
|
const bool guardPanel = TopPanelAtPoint(fx, fy, guardLane, guardKind);
|
||||||
m_activeCursor = cursorWhich;
|
|
||||||
m_cursorHover = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 网格次序拖拽:按在图例的 GX 编号徽标上即可整块拎起该网格上下移动。
|
// 网格次序拖拽:按在图例的 GX 编号徽标上即可整块拎起该网格上下移动。
|
||||||
// 位置在浮动面板之后(面板压在波形之上,应优先命中),但在信号拖拽之前。
|
if (!guardPanel)
|
||||||
{
|
{
|
||||||
const int badgeLane = LaneBadgeAtPoint(x, y);
|
const int badgeLane = LaneBadgeAtPoint(x, y);
|
||||||
if (badgeLane >= 0)
|
if (badgeLane >= 0)
|
||||||
@@ -4686,6 +4681,36 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 点击图例条目等价于在左侧列表中选中该信号(样式区随之切换目标)。
|
||||||
|
// **必须在选区放大判定之前**:选区放大模式只改变拖拽语义,
|
||||||
|
// 不应影响图例的悬停与点选(否则用户在选区放大模式下无法切换信号)。
|
||||||
|
if (!guardPanel)
|
||||||
|
{
|
||||||
|
const SignalRef legendHit = LegendAtPoint(x, y);
|
||||||
|
if (legendHit.IsValid())
|
||||||
|
{
|
||||||
|
SelectSignal(legendHit);
|
||||||
|
m_dragMode = PlotDragMode::NONE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靠近时间光标线时进入拖动。
|
||||||
|
// (放在徽标/图例之后:两者重叠时徽标与图例优先,与用户要求一致。)
|
||||||
|
if (!guardPanel)
|
||||||
|
{
|
||||||
|
int cursorWhich = 0;
|
||||||
|
if (IsOnCursorLine(x, cursorWhich) && UiPointInRect(m_area, fx, fy))
|
||||||
|
{
|
||||||
|
m_dragMode = PlotDragMode::CURSOR_MOVE;
|
||||||
|
m_hoverCursor = cursorWhich;
|
||||||
|
// 点中的光标成为“活动光标”:它触发的内容将画在上层。
|
||||||
|
m_activeCursor = cursorWhich;
|
||||||
|
m_cursorHover = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 实验性功能浮动面板(面板压在波形之上,必须先于信号拖拽判定):
|
// 实验性功能浮动面板(面板压在波形之上,必须先于信号拖拽判定):
|
||||||
// * ✕ → 关闭该网格对应的功能;
|
// * ✕ → 关闭该网格对应的功能;
|
||||||
// * 逐条目参数输入框 → 进入内联编辑;
|
// * 逐条目参数输入框 → 进入内联编辑;
|
||||||
@@ -4976,19 +5001,6 @@ namespace jdi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击图例条目等价于在左侧列表中选中该信号(样式区随之切换目标)。
|
|
||||||
// **必须在选区放大判定之前**:选区放大模式只改变拖拽语义,
|
|
||||||
// 不应影响图例的悬停与点选(否则用户在选区放大模式下无法切换信号)。
|
|
||||||
{
|
|
||||||
const SignalRef legendHit = LegendAtPoint(x, y);
|
|
||||||
if (legendHit.IsValid())
|
|
||||||
{
|
|
||||||
SelectSignal(legendHit);
|
|
||||||
m_dragMode = PlotDragMode::NONE;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PlotZoomMode mode = Tab().ZoomMode();
|
const PlotZoomMode mode = Tab().ZoomMode();
|
||||||
if (mode == PlotZoomMode::SELECT)
|
if (mode == PlotZoomMode::SELECT)
|
||||||
{
|
{
|
||||||
@@ -5330,6 +5342,10 @@ namespace jdi
|
|||||||
const std::size_t sampleCount = std::min(resolved.file->time.size(), resolved.data->values.size());
|
const std::size_t sampleCount = std::min(resolved.file->time.size(), resolved.data->values.size());
|
||||||
const double viewMin = Tab().ViewMin();
|
const double viewMin = Tab().ViewMin();
|
||||||
const double viewMax = Tab().ViewMax();
|
const double viewMax = Tab().ViewMax();
|
||||||
|
// 绘制前的数值变换(增益/绝对值/中心反转)会改变顶点,必须进缓存键。
|
||||||
|
const float gain = (resolved.style != nullptr) ? resolved.style->gain : 1.0f;
|
||||||
|
const bool absolute = (resolved.style != nullptr) && resolved.style->absolute;
|
||||||
|
const bool inverted = (resolved.style != nullptr) && resolved.style->inverted;
|
||||||
|
|
||||||
for (const PolylineCacheEntry& entry : m_polylineCache)
|
for (const PolylineCacheEntry& entry : m_polylineCache)
|
||||||
{
|
{
|
||||||
@@ -5337,7 +5353,8 @@ namespace jdi
|
|||||||
entry.viewMin == viewMin && entry.viewMax == viewMax && entry.plotX == m_plotRect.X &&
|
entry.viewMin == viewMin && entry.viewMax == viewMax && entry.plotX == m_plotRect.X &&
|
||||||
entry.plotWidth == m_plotRect.Width && entry.laneY == lane.rect.Y &&
|
entry.plotWidth == m_plotRect.Width && entry.laneY == lane.rect.Y &&
|
||||||
entry.laneHeight == lane.rect.Height && entry.laneYMin == lane.yMin &&
|
entry.laneHeight == lane.rect.Height && entry.laneYMin == lane.yMin &&
|
||||||
entry.laneYMax == lane.yMax)
|
entry.laneYMax == lane.yMax && entry.gain == gain && entry.absolute == absolute &&
|
||||||
|
entry.inverted == inverted)
|
||||||
{
|
{
|
||||||
return &entry;
|
return &entry;
|
||||||
}
|
}
|
||||||
@@ -5362,6 +5379,9 @@ namespace jdi
|
|||||||
entry.laneHeight = lane.rect.Height;
|
entry.laneHeight = lane.rect.Height;
|
||||||
entry.laneYMin = lane.yMin;
|
entry.laneYMin = lane.yMin;
|
||||||
entry.laneYMax = lane.yMax;
|
entry.laneYMax = lane.yMax;
|
||||||
|
entry.gain = gain;
|
||||||
|
entry.absolute = absolute;
|
||||||
|
entry.inverted = inverted;
|
||||||
|
|
||||||
BuildPolyline(resolved, lane, entry.points, entry.breaks, entry.band, entry.bandBreaks,
|
BuildPolyline(resolved, lane, entry.points, entry.breaks, entry.band, entry.bandBreaks,
|
||||||
entry.bandMode, entry.bandSpiky);
|
entry.bandMode, entry.bandSpiky);
|
||||||
@@ -5511,8 +5531,13 @@ namespace jdi
|
|||||||
const std::vector<double>& coarseMin = resolved.data->coarseMin;
|
const std::vector<double>& coarseMin = resolved.data->coarseMin;
|
||||||
const std::vector<double>& coarseMax = resolved.data->coarseMax;
|
const std::vector<double>& coarseMax = resolved.data->coarseMax;
|
||||||
const std::size_t coarseStride = resolved.data->coarseStride;
|
const std::size_t coarseStride = resolved.data->coarseStride;
|
||||||
|
// ⚠️ 粗粒度分组只存了逐组的 min/max:取绝对值 / 中心反转会把两者互换,
|
||||||
|
// 此时分组层无法再给出正确的包络,必须回退到逐采样点变换(见 `DrawTransformOrderPreserving`)。
|
||||||
const bool hasCoarse =
|
const bool hasCoarse =
|
||||||
(coarseStride > 0) && !coarseMin.empty() && (coarseMin.size() == coarseMax.size());
|
(coarseStride > 0) && !coarseMin.empty() && (coarseMin.size() == coarseMax.size()) &&
|
||||||
|
DrawTransformOrderPreserving(*resolved.style);
|
||||||
|
// 保序变换(正增益)下,分组极值直接乘增益即为变换后的极值(gain==1 时逐位不变)。
|
||||||
|
const double coarseScale = static_cast<double>(resolved.style->gain);
|
||||||
|
|
||||||
bool inSegment = false;
|
bool inSegment = false;
|
||||||
|
|
||||||
@@ -5566,8 +5591,8 @@ namespace jdi
|
|||||||
std::min((i1 - 1) / coarseStride, coarseMin.size() - 1);
|
std::min((i1 - 1) / coarseStride, coarseMin.size() - 1);
|
||||||
for (std::size_t bucket = firstBucket; bucket <= lastBucket; ++bucket)
|
for (std::size_t bucket = firstBucket; bucket <= lastBucket; ++bucket)
|
||||||
{
|
{
|
||||||
const double bucketLow = coarseMin[bucket];
|
const double bucketLow = coarseMin[bucket] * coarseScale;
|
||||||
const double bucketHigh = coarseMax[bucket];
|
const double bucketHigh = coarseMax[bucket] * coarseScale;
|
||||||
if (!std::isfinite(bucketLow))
|
if (!std::isfinite(bucketLow))
|
||||||
{
|
{
|
||||||
// 该分组全部是无效值:包络到此断开,后续另起一段。
|
// 该分组全部是无效值:包络到此断开,后续另起一段。
|
||||||
@@ -5590,7 +5615,7 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
for (std::size_t index = i0; index < i1; ++index)
|
for (std::size_t index = i0; index < i1; ++index)
|
||||||
{
|
{
|
||||||
const double value = values[index];
|
const double value = ApplyDrawTransform(*resolved.style, values[index]);
|
||||||
if (!std::isfinite(value))
|
if (!std::isfinite(value))
|
||||||
{
|
{
|
||||||
// 该列含无效值:包络绘制到本列结束,后续另起一段。
|
// 该列含无效值:包络绘制到本列结束,后续另起一段。
|
||||||
@@ -5624,6 +5649,7 @@ namespace jdi
|
|||||||
double boundary = 0.0;
|
double boundary = 0.0;
|
||||||
if (PolylineValueAt(time, values, count, i0, t0, boundary))
|
if (PolylineValueAt(time, values, count, i0, t0, boundary))
|
||||||
{
|
{
|
||||||
|
boundary = ApplyDrawTransform(*resolved.style, boundary);
|
||||||
if (boundary < low)
|
if (boundary < low)
|
||||||
{
|
{
|
||||||
low = boundary;
|
low = boundary;
|
||||||
@@ -5637,6 +5663,7 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
if (PolylineValueAt(time, values, count, i1, t1, boundary))
|
if (PolylineValueAt(time, values, count, i1, t1, boundary))
|
||||||
{
|
{
|
||||||
|
boundary = ApplyDrawTransform(*resolved.style, boundary);
|
||||||
if (boundary < low)
|
if (boundary < low)
|
||||||
{
|
{
|
||||||
low = boundary;
|
low = boundary;
|
||||||
@@ -5722,7 +5749,7 @@ namespace jdi
|
|||||||
for (std::size_t index = begin; index < end; index += stride)
|
for (std::size_t index = begin; index < end; index += stride)
|
||||||
{
|
{
|
||||||
const double t = time[index];
|
const double t = time[index];
|
||||||
const double value = values[index];
|
const double value = ApplyDrawTransform(*resolved.style, values[index]);
|
||||||
if (!std::isfinite(t) || !std::isfinite(value))
|
if (!std::isfinite(t) || !std::isfinite(value))
|
||||||
{
|
{
|
||||||
inSegment = false;
|
inSegment = false;
|
||||||
@@ -6386,6 +6413,10 @@ namespace jdi
|
|||||||
mixFloat(resolved.style->opacity);
|
mixFloat(resolved.style->opacity);
|
||||||
mixFloat(resolved.style->lineWidth);
|
mixFloat(resolved.style->lineWidth);
|
||||||
mix(static_cast<std::uint64_t>(resolved.style->mode));
|
mix(static_cast<std::uint64_t>(resolved.style->mode));
|
||||||
|
// 绘制前的数值变换(增益 / 取绝对值 / 中心反转)会改变波形形状。
|
||||||
|
mixFloat(resolved.style->gain);
|
||||||
|
mix(resolved.style->absolute ? 1ull : 0ull);
|
||||||
|
mix(resolved.style->inverted ? 1ull : 0ull);
|
||||||
}
|
}
|
||||||
// 图例文案参与绘制,重命名必须让缓存失效。
|
// 图例文案参与绘制,重命名必须让缓存失效。
|
||||||
mixText(resolved.data->Title());
|
mixText(resolved.data->Title());
|
||||||
@@ -6431,6 +6462,9 @@ namespace jdi
|
|||||||
entry.opacity = resolved.style->opacity;
|
entry.opacity = resolved.style->opacity;
|
||||||
entry.lineWidth = resolved.style->lineWidth;
|
entry.lineWidth = resolved.style->lineWidth;
|
||||||
entry.mode = static_cast<int>(resolved.style->mode);
|
entry.mode = static_cast<int>(resolved.style->mode);
|
||||||
|
entry.gain = resolved.style->gain;
|
||||||
|
entry.absolute = resolved.style->absolute;
|
||||||
|
entry.inverted = resolved.style->inverted;
|
||||||
}
|
}
|
||||||
lane.push_back(entry);
|
lane.push_back(entry);
|
||||||
}
|
}
|
||||||
@@ -8851,7 +8885,7 @@ namespace jdi
|
|||||||
int visibleCount = 0;
|
int visibleCount = 0;
|
||||||
int drawnCount = 0;
|
int drawnCount = 0;
|
||||||
|
|
||||||
// 先测量图例总宽度,再用半透明底板掠开底下的网格线,提升可读性。
|
// 先测量图例总宽度:只用来确定装饰缓存矩形(不再绘制任何托底/底板)。
|
||||||
float legendWidth = 0.0f;
|
float legendWidth = 0.0f;
|
||||||
for (const SignalRef& ref : laneSignals)
|
for (const SignalRef& ref : laneSignals)
|
||||||
{
|
{
|
||||||
@@ -8877,23 +8911,24 @@ namespace jdi
|
|||||||
legendWidth += itemWidth;
|
legendWidth += itemWidth;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// 底板从编号徽标开始一直盖到最后一个图例条目(编号徽标一并被掠开底下的网格线)。
|
// 底板范围(编号徽标 + 图例条目):这里**只记录装饰矩形**用于缓存贴回。
|
||||||
const float plateRight = cursorX + legendWidth + (legendWidth > 0.0f ? 2.0f * m_scale : 0.0f);
|
const float plateRight = cursorX + legendWidth + (legendWidth > 0.0f ? 2.0f * m_scale : 0.0f);
|
||||||
// 记录图例装饰矩形(含编号徽标):从徽标左缘到图例右缘。
|
|
||||||
{
|
{
|
||||||
const float left = std::min(badgeRect.X, legendBand.X - 4.0f * m_scale) - 2.0f * m_scale;
|
const float left = std::min(badgeRect.X, legendBand.X - 4.0f * m_scale) - 2.0f * m_scale;
|
||||||
const float right = std::max(badgeRect.X + badgeRect.Width, plateRight) + 2.0f * m_scale;
|
const float right = std::max(badgeRect.X + badgeRect.Width, plateRight) + 2.0f * m_scale;
|
||||||
m_decorRects.push_back(gd::RectF(left, top - 4.0f * m_scale, right - left,
|
m_decorRects.push_back(gd::RectF(left, top - 4.0f * m_scale, right - left,
|
||||||
height + 8.0f * m_scale));
|
height + 8.0f * m_scale));
|
||||||
}
|
}
|
||||||
// 命中装饰缓存:整块图例(底板 + 徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
|
// ⚠️ **不再绘制不透明托底**(用户要求去掉):原来的底板会把徽标与图例下方的
|
||||||
|
// 网格线整块擦掉,使这一条看起来像一个"可点击的控件",而它右侧的空白区域
|
||||||
|
// 实际并不响应点击(选区放大模式下会变成开始框选)。现在图例与徽标直接叠在
|
||||||
|
// 网格线之上,命中范围与看到的控件一一对应。
|
||||||
|
//
|
||||||
|
// 命中装饰缓存:整块图例(徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
|
||||||
if (m_useDecorCache)
|
if (m_useDecorCache)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UiFillRoundRect(g, gd::RectF(legendBand.X - 4.0f * m_scale, top - 2.0f * m_scale,
|
|
||||||
plateRight - legendBand.X + 4.0f * m_scale, height + 4.0f * m_scale),
|
|
||||||
3.0f * m_scale, m_theme->cardBg | 0xFF000000u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编号徽标绘制在底板之上(与信号面板的 GX 徽标使用同一套配色与圆角)。
|
// 编号徽标绘制在底板之上(与信号面板的 GX 徽标使用同一套配色与圆角)。
|
||||||
@@ -9366,6 +9401,8 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// 取值读数必须与画出来的波形一致:同样套用增益/取绝对值/中心反转。
|
||||||
|
value = ApplyDrawTransform(*resolved.style, value);
|
||||||
|
|
||||||
const float rawY = ValueToY(value, lane);
|
const float rawY = ValueToY(value, lane);
|
||||||
// 上下越界的数据点不再被丢弃:与左右越界一致,贴边绘制指示方向的三角。
|
// 上下越界的数据点不再被丢弃:与左右越界一致,贴边绘制指示方向的三角。
|
||||||
@@ -9388,6 +9425,7 @@ namespace jdi
|
|||||||
double otherValue = 0.0;
|
double otherValue = 0.0;
|
||||||
if (SampleSignalAt(*resolved.file, *resolved.data, otherTime, interpolate, otherValue))
|
if (SampleSignalAt(*resolved.file, *resolved.data, otherTime, interpolate, otherValue))
|
||||||
{
|
{
|
||||||
|
otherValue = ApplyDrawTransform(*resolved.style, otherValue);
|
||||||
const double delta = value - otherValue;
|
const double delta = value - otherValue;
|
||||||
// 增量 = 右侧光标取值 − 左侧光标取值,包在圆括号里并按差值显式给出正负号。
|
// 增量 = 右侧光标取值 − 左侧光标取值,包在圆括号里并按差值显式给出正负号。
|
||||||
text += L" (";
|
text += L" (";
|
||||||
@@ -10318,7 +10356,7 @@ namespace jdi
|
|||||||
inSegment = false;
|
inSegment = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float y = ValueToY(static_cast<double>(sampled), lane);
|
float y = ValueToY(ApplyDrawTransform(*resolved.style, static_cast<double>(sampled)), lane);
|
||||||
y = static_cast<float>(ClampDouble(y, limitTop, limitBottom));
|
y = static_cast<float>(ClampDouble(y, limitTop, limitBottom));
|
||||||
const float x = TimeToX(time[index]);
|
const float x = TimeToX(time[index]);
|
||||||
if (!inSegment)
|
if (!inSegment)
|
||||||
|
|||||||
@@ -300,7 +300,8 @@ namespace jdi
|
|||||||
WIDTH = 1, /**< 线宽数值。 */
|
WIDTH = 1, /**< 线宽数值。 */
|
||||||
OPACITY = 2, /**< 不透明度数值(百分比)。 */
|
OPACITY = 2, /**< 不透明度数值(百分比)。 */
|
||||||
SIGNAL_NAME = 3, /**< 信号显示名称(列表行内)。 */
|
SIGNAL_NAME = 3, /**< 信号显示名称(列表行内)。 */
|
||||||
FILE_NAME = 4 /**< 分组显示名称(列表行内)。 */
|
FILE_NAME = 4, /**< 分组显示名称(列表行内)。 */
|
||||||
|
GAIN = 5 /**< 系数增益(绘制前把采样值乘以该系数)。 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,6 +340,9 @@ namespace jdi
|
|||||||
OPACITY_VALUE, /**< 不透明度数值框(可点击输入)。 */
|
OPACITY_VALUE, /**< 不透明度数值框(可点击输入)。 */
|
||||||
WIDTH_SLIDER, /**< 线宽滑块。 */
|
WIDTH_SLIDER, /**< 线宽滑块。 */
|
||||||
WIDTH_VALUE, /**< 线宽数值框(可点击输入)。 */
|
WIDTH_VALUE, /**< 线宽数值框(可点击输入)。 */
|
||||||
|
GAIN_FIELD, /**< 系数增益输入框。 */
|
||||||
|
ABSOLUTE_TOGGLE, /**< 「取绝对值」切换按钮。 */
|
||||||
|
INVERT_TOGGLE, /**< 「中心反转」切换按钮。 */
|
||||||
TOGGLE_ALL, /**< 全部显示/隐藏按钮。 */
|
TOGGLE_ALL, /**< 全部显示/隐藏按钮。 */
|
||||||
FILTER_CLEAR /**< 「筛选信号」输入框右侧的清空 ✕。 */
|
FILTER_CLEAR /**< 「筛选信号」输入框右侧的清空 ✕。 */
|
||||||
};
|
};
|
||||||
@@ -592,13 +596,17 @@ namespace jdi
|
|||||||
|
|
||||||
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
|
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
|
||||||
|
|
||||||
// 样式区固定四行:颜色 / 样式 / 透明度 / 线宽。各行控件共用同一内容列宽度与左边界。
|
// 样式区固定六行:颜色 / 样式 / 线宽 / 透明度 / 系数增益 / 取绝对值与中心反转。
|
||||||
std::array<gd::RectF, 4> m_styleRowRects; /**< 四行整体矩形(含标签列)。 */
|
// 各行控件共用同一内容列宽度与左边界。
|
||||||
|
std::array<gd::RectF, 6> m_styleRowRects; /**< 六行整体矩形(含标签列)。 */
|
||||||
|
|
||||||
gd::RectF m_opacityTrackRect; /**< 不透明度滑块轨道矩形。 */
|
gd::RectF m_opacityTrackRect; /**< 不透明度滑块轨道矩形。 */
|
||||||
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
|
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
|
||||||
gd::RectF m_widthTrackRect; /**< 线宽滑块轨道矩形。 */
|
gd::RectF m_widthTrackRect; /**< 线宽滑块轨道矩形。 */
|
||||||
gd::RectF m_widthValueRect; /**< 线宽数值文本矩形(双击可输入)。 */
|
gd::RectF m_widthValueRect; /**< 线宽数值文本矩形(双击可输入)。 */
|
||||||
|
gd::RectF m_gainFieldRect; /**< 系数增益输入框矩形。 */
|
||||||
|
gd::RectF m_absoluteRect; /**< 「取绝对值」切换按钮矩形。 */
|
||||||
|
gd::RectF m_invertedRect; /**< 「中心反转」切换按钮矩形。 */
|
||||||
|
|
||||||
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
|
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
|
||||||
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
|
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
|
||||||
|
|||||||
@@ -88,15 +88,15 @@ namespace jdi
|
|||||||
return p == pattern.size();
|
return p == pattern.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 样式编辑区:固定四行网格(颜色 / 样式 / 透明度 / 线宽),行高、间距与内容列宽度统一,
|
// 样式编辑区:固定六行网格(颜色 / 样式 / 线宽 / 透明度 / 系数增益 / 取绝对值与中心反转),
|
||||||
// 保证面板宽度变化时四行控件始终对齐,且不会被无限制拉伸。
|
// 行高、间距与内容列宽度统一,保证面板宽度变化时各行控件始终对齐,且不会被无限制拉伸。
|
||||||
const float kStylePadH = 12.0f; /**< 样式区左右内边距。 */
|
const float kStylePadH = 12.0f; /**< 样式区左右内边距。 */
|
||||||
const float kStylePadV = 12.0f; /**< 样式区上下内边距。 */
|
const float kStylePadV = 12.0f; /**< 样式区上下内边距。 */
|
||||||
const float kStyleRowHeight = 26.0f; /**< 样式区单行高度。 */
|
const float kStyleRowHeight = 26.0f; /**< 样式区单行高度。 */
|
||||||
const float kStyleRowGap = 10.0f; /**< 样式区行间距。 */
|
const float kStyleRowGap = 10.0f; /**< 样式区行间距。 */
|
||||||
const float kStyleLabelWidth = 44.0f; /**< 样式行标签列宽度。 */
|
const float kStyleLabelWidth = 56.0f; /**< 样式行标签列宽度(需容纳「系数增益」四个字)。 */
|
||||||
const float kStyleLabelGap = 8.0f; /**< 标签列与内容列间距。 */
|
const float kStyleLabelGap = 8.0f; /**< 标签列与内容列间距。 */
|
||||||
const float kStyleRowCount = 4.0f; /**< 样式区固定行数。 */
|
const float kStyleRowCount = 6.0f; /**< 样式区固定行数。 */
|
||||||
const float kStyleContentMax = 260.0f; /**< 内容列最大宽度(再拉宽面板也不再延伸)。 */
|
const float kStyleContentMax = 260.0f; /**< 内容列最大宽度(再拉宽面板也不再延伸)。 */
|
||||||
const float kSwatchEdge = 18.0f; /**< 颜色色块边长上限(含自定义颜色入口)。 */
|
const float kSwatchEdge = 18.0f; /**< 颜色色块边长上限(含自定义颜色入口)。 */
|
||||||
const float kSwatchMin = 11.0f; /**< 颜色色块边长下限。 */
|
const float kSwatchMin = 11.0f; /**< 颜色色块边长下限。 */
|
||||||
@@ -105,10 +105,16 @@ namespace jdi
|
|||||||
const float kSliderValueWidth = 52.0f; /**< 滑块行数值文本宽度(透明度与线宽一致)。 */
|
const float kSliderValueWidth = 52.0f; /**< 滑块行数值文本宽度(透明度与线宽一致)。 */
|
||||||
const float kSliderValueGap = 8.0f; /**< 滑块轨道与数值文本间距。 */
|
const float kSliderValueGap = 8.0f; /**< 滑块轨道与数值文本间距。 */
|
||||||
const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
|
const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
|
||||||
|
const float kGainFieldWidth = 110.0f; /**< 系数增益输入框宽度(逻辑像素)。 */
|
||||||
|
const float kGainFieldPadH = 6.0f; /**< 系数增益输入框内文字左右内缩。 */
|
||||||
|
const float kStyleFlagGap = 6.0f; /**< 「取绝对值」与「中心反转」两个按钮之间的间距。 */
|
||||||
|
|
||||||
const float kWidthMin = 0.5f; /**< 允许的最小线宽(px)。 */
|
const float kWidthMin = 0.5f; /**< 允许的最小线宽(px)。 */
|
||||||
const float kWidthMax = 10.0f; /**< 允许的最大线宽(px)。 */
|
const float kWidthMax = 10.0f; /**< 允许的最大线宽(px)。 */
|
||||||
|
|
||||||
|
/** @brief 系数增益允许的绝对值上限(防止输入极端值把波形完全挤出可视区)。 */
|
||||||
|
const double kGainLimit = 10000.0;
|
||||||
|
|
||||||
/** @brief 可选绘制样式顺序(不含阶梯方式)。 */
|
/** @brief 可选绘制样式顺序(不含阶梯方式)。 */
|
||||||
const DrawMode kModeOptions[] = {DrawMode::LINE, DrawMode::SCATTER, DrawMode::LINE_SCATTER};
|
const DrawMode kModeOptions[] = {DrawMode::LINE, DrawMode::SCATTER, DrawMode::LINE_SCATTER};
|
||||||
const int kModeOptionCount = static_cast<int>(sizeof(kModeOptions) / sizeof(kModeOptions[0]));
|
const int kModeOptionCount = static_cast<int>(sizeof(kModeOptions) / sizeof(kModeOptions[0]));
|
||||||
@@ -149,6 +155,33 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
return (value < low) ? low : ((value > high) ? high : value);
|
return (value < low) ? low : ((value > high) ? high : value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 把系数增益格式化为界面文本(最多 4 位小数,并去掉无意义的尾随 0 与小数点)。
|
||||||
|
*
|
||||||
|
* 默认值 1 因此显示为 `1`(而不是 `1.0000`),输入 2.5 显示为 `2.5`。
|
||||||
|
*
|
||||||
|
* @param [IN] gain 增益系数。
|
||||||
|
* @return 显示文本。
|
||||||
|
*/
|
||||||
|
std::wstring FormatGainText(float gain)
|
||||||
|
{
|
||||||
|
std::wstring text = jdi::FormatNumber(static_cast<double>(gain), 4);
|
||||||
|
if (text.find(L'.') != std::wstring::npos)
|
||||||
|
{
|
||||||
|
std::size_t end = text.size();
|
||||||
|
while (end > 0 && text[end - 1] == L'0')
|
||||||
|
{
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
if (end > 0 && text[end - 1] == L'.')
|
||||||
|
{
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
text.erase(end);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalPanel::SignalPanel()
|
SignalPanel::SignalPanel()
|
||||||
@@ -257,6 +290,16 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
return LoadCursorW(nullptr, IDC_HAND);
|
return LoadCursorW(nullptr, IDC_HAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 样式区的可点击控件(系数增益输入框、两个切换按钮、数值框)也用手型光标。
|
||||||
|
const float fx = static_cast<float>(x);
|
||||||
|
const float fy = static_cast<float>(y);
|
||||||
|
if (UiPointInRect(m_gainFieldRect, fx, fy) || UiPointInRect(m_absoluteRect, fx, fy) ||
|
||||||
|
UiPointInRect(m_invertedRect, fx, fy) || UiPointInRect(m_widthValueRect, fx, fy) ||
|
||||||
|
UiPointInRect(m_opacityValueRect, fx, fy))
|
||||||
|
{
|
||||||
|
return LoadCursorW(nullptr, IDC_HAND);
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,8 +677,10 @@ namespace jdi
|
|||||||
|
|
||||||
const gd::RectF& colorRow = m_styleRowRects[0];
|
const gd::RectF& colorRow = m_styleRowRects[0];
|
||||||
const gd::RectF& modeRow = m_styleRowRects[1];
|
const gd::RectF& modeRow = m_styleRowRects[1];
|
||||||
const gd::RectF& opacityRow = m_styleRowRects[2];
|
const gd::RectF& widthRow = m_styleRowRects[2];
|
||||||
const gd::RectF& widthRow = m_styleRowRects[3];
|
const gd::RectF& opacityRow = m_styleRowRects[3];
|
||||||
|
const gd::RectF& gainRow = m_styleRowRects[4];
|
||||||
|
const gd::RectF& flagRow = m_styleRowRects[5];
|
||||||
|
|
||||||
// ---- 颜色行:预设色块 + 同尺寸自定义颜色入口(合计恰好铺满内容列宽度)----
|
// ---- 颜色行:预设色块 + 同尺寸自定义颜色入口(合计恰好铺满内容列宽度)----
|
||||||
int paletteCount = 0;
|
int paletteCount = 0;
|
||||||
@@ -689,20 +734,30 @@ namespace jdi
|
|||||||
modeWidth, rowHeight);
|
modeWidth, rowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 透明度行与线宽行:几何完全一致,保证纵向宽度对齐 ----
|
// ---- 线宽行与透明度行:几何完全一致,保证纵向宽度对齐(线宽在上、透明度在下)----
|
||||||
const float valueWidth = std::min(kSliderValueWidth * m_scale, contentWidth * 0.4f);
|
const float valueWidth = std::min(kSliderValueWidth * m_scale, contentWidth * 0.4f);
|
||||||
const float trackWidth = std::max(24.0f, contentWidth - valueWidth - kSliderValueGap * m_scale);
|
const float trackWidth = std::max(24.0f, contentWidth - valueWidth - kSliderValueGap * m_scale);
|
||||||
const float trackHeight = kTrackHeight * m_scale;
|
const float trackHeight = kTrackHeight * m_scale;
|
||||||
|
|
||||||
|
m_widthTrackRect =
|
||||||
|
gd::RectF(contentLeft, widthRow.Y + (rowHeight - trackHeight) * 0.5f, trackWidth, trackHeight);
|
||||||
|
m_widthValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, widthRow.Y,
|
||||||
|
valueWidth, rowHeight);
|
||||||
|
|
||||||
m_opacityTrackRect = gd::RectF(contentLeft, opacityRow.Y + (rowHeight - trackHeight) * 0.5f,
|
m_opacityTrackRect = gd::RectF(contentLeft, opacityRow.Y + (rowHeight - trackHeight) * 0.5f,
|
||||||
trackWidth, trackHeight);
|
trackWidth, trackHeight);
|
||||||
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, opacityRow.Y,
|
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, opacityRow.Y,
|
||||||
valueWidth, rowHeight);
|
valueWidth, rowHeight);
|
||||||
|
|
||||||
m_widthTrackRect =
|
// ---- 系数增益行:靠左的数值输入框 ----
|
||||||
gd::RectF(contentLeft, widthRow.Y + (rowHeight - trackHeight) * 0.5f, trackWidth, trackHeight);
|
const float gainWidth = std::min(kGainFieldWidth * m_scale, contentWidth);
|
||||||
m_widthValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, widthRow.Y,
|
m_gainFieldRect = gd::RectF(contentLeft, gainRow.Y, gainWidth, rowHeight);
|
||||||
valueWidth, rowHeight);
|
|
||||||
|
// ---- 「取绝对值」与「中心反转」:同一行的两个等宽切换按钮 ----
|
||||||
|
const float flagGap = kStyleFlagGap * m_scale;
|
||||||
|
const float flagWidth = std::max(20.0f, (contentWidth - flagGap) * 0.5f);
|
||||||
|
m_absoluteRect = gd::RectF(contentLeft, flagRow.Y, flagWidth, rowHeight);
|
||||||
|
m_invertedRect = gd::RectF(contentLeft + flagWidth + flagGap, flagRow.Y, flagWidth, rowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalPanel::Layout(const gd::RectF& area)
|
void SignalPanel::Layout(const gd::RectF& area)
|
||||||
@@ -1207,29 +1262,9 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 第三行:透明度(数值可点击输入)----
|
// ---- 第三行:线宽(数值可点击输入)----
|
||||||
{
|
{
|
||||||
paintRowLabel(2, L"透明度");
|
paintRowLabel(2, L"线宽");
|
||||||
|
|
||||||
const bool active = m_draggingOpacity || (m_hoverStyle == StyleHit::OPACITY_SLIDER);
|
|
||||||
UiDrawSlider(g, m_opacityTrackRect, static_cast<double>(style->opacity), m_theme->accent, active,
|
|
||||||
m_scale);
|
|
||||||
|
|
||||||
if (m_editField == StyleEditField::OPACITY)
|
|
||||||
{
|
|
||||||
UiDrawEditField(g, m_opacityValueRect, m_edit, m_fonts->monoSmall, m_scale);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const int percent = static_cast<int>(style->opacity * 100.0f + 0.5f);
|
|
||||||
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
|
|
||||||
m_theme->textStrong, TextAlign::RIGHT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 第四行:线宽(与透明度行几何一致,数值可点击输入)----
|
|
||||||
{
|
|
||||||
paintRowLabel(3, L"线宽");
|
|
||||||
|
|
||||||
const double ratio =
|
const double ratio =
|
||||||
(static_cast<double>(style->lineWidth) - kWidthMin) / (kWidthMax - kWidthMin);
|
(static_cast<double>(style->lineWidth) - kWidthMin) / (kWidthMax - kWidthMin);
|
||||||
@@ -1253,6 +1288,65 @@ namespace jdi
|
|||||||
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong, TextAlign::RIGHT);
|
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong, TextAlign::RIGHT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 第四行:透明度(数值可点击输入)----
|
||||||
|
{
|
||||||
|
paintRowLabel(3, L"透明度");
|
||||||
|
|
||||||
|
const bool active = m_draggingOpacity || (m_hoverStyle == StyleHit::OPACITY_SLIDER);
|
||||||
|
UiDrawSlider(g, m_opacityTrackRect, static_cast<double>(style->opacity), m_theme->accent, active,
|
||||||
|
m_scale);
|
||||||
|
|
||||||
|
if (m_editField == StyleEditField::OPACITY)
|
||||||
|
{
|
||||||
|
UiDrawEditField(g, m_opacityValueRect, m_edit, m_fonts->monoSmall, m_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int percent = static_cast<int>(style->opacity * 100.0f + 0.5f);
|
||||||
|
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
|
||||||
|
m_theme->textStrong, TextAlign::RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 第五行:系数增益(绘制前把采样值乘以该系数,可点击输入)----
|
||||||
|
{
|
||||||
|
paintRowLabel(4, L"系数增益");
|
||||||
|
|
||||||
|
if (m_editField == StyleEditField::GAIN)
|
||||||
|
{
|
||||||
|
UiDrawEditField(g, m_gainFieldRect, m_edit, m_fonts->monoSmall, m_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const bool hovered = (m_hoverStyle == StyleHit::GAIN_FIELD);
|
||||||
|
UiFillRoundRect(g, m_gainFieldRect, kCorner * m_scale, m_theme->cardBg);
|
||||||
|
UiDrawRoundRect(g, m_gainFieldRect, kCorner * m_scale,
|
||||||
|
hovered ? m_theme->accent : m_theme->border, hovered ? 1.6f : 1.0f);
|
||||||
|
const gd::RectF textRect(m_gainFieldRect.X + kGainFieldPadH * m_scale, m_gainFieldRect.Y,
|
||||||
|
std::max(8.0f, m_gainFieldRect.Width - kGainFieldPadH * 2.0f * m_scale),
|
||||||
|
m_gainFieldRect.Height);
|
||||||
|
UiText(g, FormatGainText(style->gain), m_fonts->monoSmall, textRect, m_theme->textStrong,
|
||||||
|
TextAlign::LEFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 第六行:取绝对值 / 中心反转(两个切换按钮,作用顺序为「先绝对值、后中心反转」)----
|
||||||
|
{
|
||||||
|
const auto paintFlag = [&](const gd::RectF& rect, const wchar_t* label, bool checked,
|
||||||
|
StyleHit target)
|
||||||
|
{
|
||||||
|
const bool hot = (m_hoverStyle == target);
|
||||||
|
UiFillRoundRect(g, rect, kCorner * m_scale,
|
||||||
|
checked ? m_theme->accentSoft : (hot ? m_theme->hover : m_theme->cardBg));
|
||||||
|
UiDrawRoundRect(g, rect, kCorner * m_scale,
|
||||||
|
checked ? m_theme->accent : m_theme->border, 1.0f);
|
||||||
|
UiText(g, label, m_fonts->uiSmall, rect,
|
||||||
|
checked ? m_theme->textStrong : m_theme->text, TextAlign::CENTER);
|
||||||
|
};
|
||||||
|
paintFlag(m_absoluteRect, L"取绝对值", style->absolute, StyleHit::ABSOLUTE_TOGGLE);
|
||||||
|
paintFlag(m_invertedRect, L"中心反转", style->inverted, StyleHit::INVERT_TOGGLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SignalPanel::AreAllSignalsVisible() const
|
bool SignalPanel::AreAllSignalsVisible() const
|
||||||
@@ -1372,6 +1466,16 @@ namespace jdi
|
|||||||
initial = std::to_wstring(static_cast<int>(style->opacity * 100.0f + 0.5f));
|
initial = std::to_wstring(static_cast<int>(style->opacity * 100.0f + 0.5f));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case StyleEditField::GAIN:
|
||||||
|
{
|
||||||
|
const SignalStyle* style = StyleOf(SelectedRef());
|
||||||
|
if (style == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
initial = FormatGainText(style->gain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case StyleEditField::SIGNAL_NAME:
|
case StyleEditField::SIGNAL_NAME:
|
||||||
{
|
{
|
||||||
if (row < 0 || row >= static_cast<int>(m_rows.size()))
|
if (row < 0 || row >= static_cast<int>(m_rows.size()))
|
||||||
@@ -1476,6 +1580,25 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case StyleEditField::GAIN:
|
||||||
|
{
|
||||||
|
if (trimmed.empty())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wchar_t* stop = nullptr;
|
||||||
|
const double parsed = std::wcstod(trimmed.c_str(), &stop);
|
||||||
|
if (stop == trimmed.c_str() || !std::isfinite(parsed))
|
||||||
|
{
|
||||||
|
break; // 非法输入保持原值
|
||||||
|
}
|
||||||
|
if (SignalStyle* style = StyleOf(SelectedRef()))
|
||||||
|
{
|
||||||
|
style->gain =
|
||||||
|
static_cast<float>(std::max(-kGainLimit, std::min(parsed, kGainLimit)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case StyleEditField::SIGNAL_NAME:
|
case StyleEditField::SIGNAL_NAME:
|
||||||
{
|
{
|
||||||
if (row < 0 || row >= static_cast<int>(m_rows.size()) || m_dataSetWritable == nullptr)
|
if (row < 0 || row >= static_cast<int>(m_rows.size()) || m_dataSetWritable == nullptr)
|
||||||
@@ -1530,6 +1653,8 @@ namespace jdi
|
|||||||
return m_widthValueRect;
|
return m_widthValueRect;
|
||||||
case StyleEditField::OPACITY:
|
case StyleEditField::OPACITY:
|
||||||
return m_opacityValueRect;
|
return m_opacityValueRect;
|
||||||
|
case StyleEditField::GAIN:
|
||||||
|
return m_gainFieldRect;
|
||||||
case StyleEditField::SIGNAL_NAME:
|
case StyleEditField::SIGNAL_NAME:
|
||||||
case StyleEditField::FILE_NAME:
|
case StyleEditField::FILE_NAME:
|
||||||
return RowNameRect(m_editRow);
|
return RowNameRect(m_editRow);
|
||||||
@@ -1986,6 +2111,18 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
hoverStyle = StyleHit::WIDTH_VALUE;
|
hoverStyle = StyleHit::WIDTH_VALUE;
|
||||||
}
|
}
|
||||||
|
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_gainFieldRect, fx, fy))
|
||||||
|
{
|
||||||
|
hoverStyle = StyleHit::GAIN_FIELD;
|
||||||
|
}
|
||||||
|
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_absoluteRect, fx, fy))
|
||||||
|
{
|
||||||
|
hoverStyle = StyleHit::ABSOLUTE_TOGGLE;
|
||||||
|
}
|
||||||
|
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_invertedRect, fx, fy))
|
||||||
|
{
|
||||||
|
hoverStyle = StyleHit::INVERT_TOGGLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoverRow == m_hoverRow && hoverStyle == m_hoverStyle && hoverStyleIndex == m_hoverStyleIndex &&
|
if (hoverRow == m_hoverRow && hoverStyle == m_hoverStyle && hoverStyleIndex == m_hoverStyleIndex &&
|
||||||
@@ -2126,6 +2263,22 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
return BeginEdit(StyleEditField::OPACITY);
|
return BeginEdit(StyleEditField::OPACITY);
|
||||||
}
|
}
|
||||||
|
// 系数增益:点击输入框即进入输入状态。
|
||||||
|
if (UiPointInRect(m_gainFieldRect, fx, fy))
|
||||||
|
{
|
||||||
|
return BeginEdit(StyleEditField::GAIN);
|
||||||
|
}
|
||||||
|
// 「取绝对值」/「中心反转」:单击即切换(作用顺序为 增益 → 绝对值 → 中心反转)。
|
||||||
|
if (UiPointInRect(m_absoluteRect, fx, fy))
|
||||||
|
{
|
||||||
|
style->absolute = !style->absolute;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (UiPointInRect(m_invertedRect, fx, fy))
|
||||||
|
{
|
||||||
|
style->inverted = !style->inverted;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 列表行。
|
// 列表行。
|
||||||
@@ -2306,6 +2459,10 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
return BeginEdit(StyleEditField::OPACITY);
|
return BeginEdit(StyleEditField::OPACITY);
|
||||||
}
|
}
|
||||||
|
if (UiPointInRect(m_gainFieldRect, fx, fy))
|
||||||
|
{
|
||||||
|
return BeginEdit(StyleEditField::GAIN);
|
||||||
|
}
|
||||||
|
|
||||||
RowPart part = RowPart::NONE;
|
RowPart part = RowPart::NONE;
|
||||||
const int row = HitRow(x, y, part);
|
const int row = HitRow(x, y, part);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ namespace jdi
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 单个信号的显示外观(颜色/透明度/线宽/绘制方式)。
|
* @brief 单个信号的显示外观(颜色/透明度/线宽/绘制方式/数值变换)。
|
||||||
*
|
*
|
||||||
* 外观属于信号自身而非某个标签页——同一信号在所有标签页中必须显示效果一致,
|
* 外观属于信号自身而非某个标签页——同一信号在所有标签页中必须显示效果一致,
|
||||||
* 因此在任一视图里修改颜色/线宽/透明度/样式,其它视图会自动同步。
|
* 因此在任一视图里修改颜色/线宽/透明度/样式,其它视图会自动同步。
|
||||||
@@ -103,8 +103,60 @@ namespace jdi
|
|||||||
float opacity = 1.0f; /**< 不透明度,取值 0.0-1.0,默认 1.0。 */
|
float opacity = 1.0f; /**< 不透明度,取值 0.0-1.0,默认 1.0。 */
|
||||||
float lineWidth = 1.0f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
|
float lineWidth = 1.0f; /**< 线宽(逻辑像素,未乘 DPI 缩放)。 */
|
||||||
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
|
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制前的数值变换(三者**依次**作用,见 `ApplyDrawTransform`):
|
||||||
|
* 1. `gain` 系数增益:采样值乘以该系数(默认 1,即不改变);
|
||||||
|
* 2. `absolute` 取绝对值:|value|;
|
||||||
|
* 3. `inverted` 中心反转:按 Y = 0 上下反向(value → −value)。
|
||||||
|
*/
|
||||||
|
float gain = 1.0f; /**< 系数增益,默认 1.0。 */
|
||||||
|
bool absolute = false; /**< 是否对绘制值取绝对值,默认关闭。 */
|
||||||
|
bool inverted = false; /**< 是否按 Y=0 中心反转绘制值,默认关闭。 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 把原始采样值变换为**绘制用值**。
|
||||||
|
*
|
||||||
|
* 顺序固定为「系数增益 → 取绝对值 → 中心反转」(用户明确要求中心反转最后结算),
|
||||||
|
* 例如增益 2、原始值 1.5 → 2×1.5 = 3;再取绝对值仍为 3;再中心反转为 −3。
|
||||||
|
* NaN 经乘/取绝对值/取反后仍为 NaN(保持断线语义)。
|
||||||
|
*
|
||||||
|
* @param [IN] style 信号外观(提供增益与两个开关)。
|
||||||
|
* @param [IN] value 原始采样值。
|
||||||
|
* @return 绘制用值。
|
||||||
|
*/
|
||||||
|
inline double ApplyDrawTransform(const SignalStyle& style, double value)
|
||||||
|
{
|
||||||
|
if (style.gain != 1.0f)
|
||||||
|
{
|
||||||
|
value *= static_cast<double>(style.gain);
|
||||||
|
}
|
||||||
|
if (style.absolute)
|
||||||
|
{
|
||||||
|
value = (value < 0.0) ? -value : value;
|
||||||
|
}
|
||||||
|
if (style.inverted)
|
||||||
|
{
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断数值变换是否**保持大小顺序**(正增益、且不取绝对值、不反转)。
|
||||||
|
*
|
||||||
|
* 只有保序时,粗粒度包络层(每组的 min/max)才能直接按增益缩放后继续使用;
|
||||||
|
* 取绝对值 / 反转会把最小值与最大值互换,此时必须回退到逐采样点变换。
|
||||||
|
*
|
||||||
|
* @param [IN] style 信号外观。
|
||||||
|
* @return 变换单调递增返回 true。
|
||||||
|
*/
|
||||||
|
inline bool DrawTransformOrderPreserving(const SignalStyle& style)
|
||||||
|
{
|
||||||
|
return (style.gain > 0.0f) && !style.absolute && !style.inverted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 单条信号的元数据与采样值(纯数据,不含可见性配置)。
|
* @brief 单条信号的元数据与采样值(纯数据,不含可见性配置)。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ namespace jdi
|
|||||||
const float kTitleGap = 8.0f; /**< 标题与内容区间距。 */
|
const float kTitleGap = 8.0f; /**< 标题与内容区间距。 */
|
||||||
const float kRowHeight = 26.0f; /**< 单个滑块行高度。 */
|
const float kRowHeight = 26.0f; /**< 单个滑块行高度。 */
|
||||||
const float kRowGap = 2.0f; /**< 滑块行间距。 */
|
const float kRowGap = 2.0f; /**< 滑块行间距。 */
|
||||||
const float kPreviewGap = 10.0f; /**< 滑块区与预览区间距。 */
|
const float kPreviewHeight = 24.0f; /**< 预览行(预览色块 + 色号)高度。 */
|
||||||
const float kPreviewHeight = 24.0f; /**< 预览行高度。 */
|
|
||||||
const float kLabelWidth = 16.0f; /**< 通道标签宽度。 */
|
const float kLabelWidth = 16.0f; /**< 通道标签宽度。 */
|
||||||
const float kLabelGap = 6.0f; /**< 标签与滑块间距。 */
|
const float kLabelGap = 6.0f; /**< 标签与滑块间距。 */
|
||||||
const float kValueWidth = 40.0f; /**< 数值文本宽度。 */
|
const float kValueWidth = 40.0f; /**< 数值文本宽度。 */
|
||||||
@@ -220,13 +219,14 @@ namespace jdi
|
|||||||
|
|
||||||
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
|
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
|
||||||
{
|
{
|
||||||
// 布局:左侧一列放"色域方块 + 色相条",右侧一列放 R/G/B 三行
|
// 布局:左侧一列放"色域方块 + 色相条",右侧一列**上方**放"预览色块 + 色号"、
|
||||||
//(每行 = 通道标签 + 滑块 + 数值),两列下方再放"预览色块 + 色号"一行。
|
// **下方**放 R/G/B 三行(每行 = 通道标签 + 滑块 + 数值)。
|
||||||
|
// 右列总高 = 预览行 + 间隙 + 三行滑块,与左侧"色域方块"高度**严格相等**,
|
||||||
|
// 因此两列上下边对齐、整体版面协调(不再有单起一行的底部预览条)。
|
||||||
// 所有控件都从面板内边距起算,因此不会出现越界。
|
// 所有控件都从面板内边距起算,因此不会出现越界。
|
||||||
const float width = kPanelWidth * m_scale;
|
const float width = kPanelWidth * m_scale;
|
||||||
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + kGamutSize + kPreviewGap +
|
const float height =
|
||||||
kPreviewHeight) *
|
(kPadV * 2.0f + kTitleHeight + kTitleGap + kGamutSize) * m_scale;
|
||||||
m_scale;
|
|
||||||
|
|
||||||
const float gap = 4.0f * m_scale;
|
const float gap = 4.0f * m_scale;
|
||||||
|
|
||||||
@@ -260,22 +260,30 @@ namespace jdi
|
|||||||
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
|
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
|
||||||
kHueWidth * m_scale, kGamutSize * m_scale);
|
kHueWidth * m_scale, kGamutSize * m_scale);
|
||||||
|
|
||||||
// 右列:R/G/B 三行。整列在方块高度内纵向居中,与左侧方块视觉平衡。
|
// 右列:预览行在滑块之上,整列高度与左侧方块一致。
|
||||||
const float leftColumnWidth =
|
const float leftColumnWidth = (kGamutSize + kGamutGap + kHueWidth) * m_scale;
|
||||||
(kGamutSize + kGamutGap + kHueWidth) * m_scale;
|
|
||||||
const float columnLeft = m_gamutRect.X + leftColumnWidth + kColumnGap * m_scale;
|
const float columnLeft = m_gamutRect.X + leftColumnWidth + kColumnGap * m_scale;
|
||||||
const float columnWidth = std::max(60.0f * m_scale, contentRight - columnLeft);
|
const float columnWidth = std::max(60.0f * m_scale, contentRight - columnLeft);
|
||||||
const float rowsHeight = (kRowHeight * 3.0f + kRowGap * 2.0f) * m_scale;
|
|
||||||
const float firstRowTop =
|
|
||||||
contentTop + std::max(0.0f, (kGamutSize * m_scale - rowsHeight) * 0.5f);
|
|
||||||
|
|
||||||
|
// 预览行与滑块之间的间隙:正好补齐"方块高度 − 预览行高 − 三行滑块高",
|
||||||
|
// 使右列总高与左侧色域方块严格相等(常量变化时自动适配)。
|
||||||
|
const float previewRowGap =
|
||||||
|
std::max(0.0f, kGamutSize - kPreviewHeight - (kRowHeight * 3.0f + kRowGap * 2.0f)) * m_scale;
|
||||||
|
|
||||||
|
m_previewRect = gd::RectF(columnLeft, contentTop, kSwatchWidth * m_scale, kPreviewHeight * m_scale);
|
||||||
|
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + kSwatchHexGap * m_scale, contentTop,
|
||||||
|
std::max(40.0f * m_scale,
|
||||||
|
columnWidth - (kSwatchWidth + kSwatchHexGap) * m_scale),
|
||||||
|
kPreviewHeight * m_scale);
|
||||||
|
|
||||||
|
const float sliderTop = m_previewRect.Y + m_previewRect.Height + previewRowGap;
|
||||||
const float trackLeft = columnLeft + (kLabelWidth + kLabelGap) * m_scale;
|
const float trackLeft = columnLeft + (kLabelWidth + kLabelGap) * m_scale;
|
||||||
const float trackWidth =
|
const float trackWidth =
|
||||||
std::max(24.0f, columnWidth - (kLabelWidth + kLabelGap + kValueWidth + 6.0f) * m_scale);
|
std::max(24.0f, columnWidth - (kLabelWidth + kLabelGap + kValueWidth + 6.0f) * m_scale);
|
||||||
|
|
||||||
for (int index = 0; index < 3; ++index)
|
for (int index = 0; index < 3; ++index)
|
||||||
{
|
{
|
||||||
const float rowTop = firstRowTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
const float rowTop = sliderTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
||||||
m_tracks[static_cast<std::size_t>(index)] =
|
m_tracks[static_cast<std::size_t>(index)] =
|
||||||
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
||||||
trackWidth, kTrackHeight * m_scale);
|
trackWidth, kTrackHeight * m_scale);
|
||||||
@@ -283,15 +291,6 @@ namespace jdi
|
|||||||
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop, kValueWidth * m_scale,
|
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop, kValueWidth * m_scale,
|
||||||
kRowHeight * m_scale);
|
kRowHeight * m_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下行:预览色块 + 色号,与上方的"方块 + 色相条"同宽。
|
|
||||||
const float previewTop = contentTop + (kGamutSize + kPreviewGap) * m_scale;
|
|
||||||
m_previewRect =
|
|
||||||
gd::RectF(contentLeft, previewTop, kSwatchWidth * m_scale, kPreviewHeight * m_scale);
|
|
||||||
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + kSwatchHexGap * m_scale,
|
|
||||||
previewTop,
|
|
||||||
std::max(40.0f * m_scale, leftColumnWidth - (kSwatchWidth + kSwatchHexGap) * m_scale),
|
|
||||||
kPreviewHeight * m_scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorPickerPopup::Open(const gd::RectF& bounds, const gd::RectF& anchor, std::uint32_t color)
|
void ColorPickerPopup::Open(const gd::RectF& bounds, const gd::RectF& anchor, std::uint32_t color)
|
||||||
|
|||||||
Reference in new issue
Block a user