标题栏内存占用 / 样式区增益与取绝对值·中心反转 / 拾色器排版 / 图例去托底与命中优先级

标题栏:
- 应用名称后追加「  |  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:
DeepSeek V4.1 Flash committed 2026-10-13 12:06:38 +08:00
1 parent 20b90848fe
commit c3343907ec
8 files changed
+420 -105

No files matched your search

+9 -1
View File
@@ -1838,6 +1838,9 @@ namespace jdi
float laneHeight = 0.0f; /**< 网格高度。 */
double laneYMin = 0.0; /**< 网格幅值下界。 */
double laneYMax = 0.0; /**< 网格幅值上界。 */
float gain = 1.0f; /**< 绘制前的系数增益。 */
bool absolute = false; /**< 绘制前是否取绝对值。 */
bool inverted = false; /**< 绘制前是否中心反转。 */
std::vector<gd::PointF> points; /**< 折线顶点。 */
std::vector<std::size_t> breaks; /**< 每段折线在 points 中的起始下标。 */
@@ -2001,12 +2004,17 @@ namespace jdi
float opacity = 1.0f; /**< 不透明度。 */
float lineWidth = 1.0f; /**< 线宽。 */
int mode = 0; /**< 绘制模式。 */
float gain = 1.0f; /**< 系数增益。 */
bool absolute = false; /**< 是否取绝对值。 */
bool inverted = false; /**< 是否中心反转。 */
/** @brief 相等比较(用于判断"逐信号外观样式是否变化")。 */
bool operator==(const SceneStyleEntry& other) const
{
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;
}
};
+79 -41
View File
@@ -4654,22 +4654,17 @@ namespace jdi
}
}
// 靠近时间光标线时优先进入拖动,避免误触发平移或缩放选区。
{
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;
}
}
// 图例与 GX 编号徽标叠在波形之上、且直接位于网格顶部——用户明确要求它们**优先响应**:
// 必须先于「时间光标线 / 光标读数 / 边沿计数横向光标 / 选区放大」判定,否则:
// * 光标线恰好经过徽标/图例时,按下会变成拖光标;
// * 选区放大模式下会变成“开始框选”,用户看到的就是“GX 拖不动、图例点不中”。
// 层序上仍然让位于浮动面板(面板绘制在徽标与图例之上),因此先挡一次面板命中。
int guardLane = -1;
int guardKind = 0;
const bool guardPanel = TopPanelAtPoint(fx, fy, guardLane, guardKind);
// 网格次序拖拽:按在图例的 GX 编号徽标上即可整块拎起该网格上下移动。
// 位置在浮动面板之后(面板压在波形之上,应优先命中),但在信号拖拽之前。
if (!guardPanel)
{
const int badgeLane = LaneBadgeAtPoint(x, y);
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;
}
// 点击图例条目等价于在左侧列表中选中该信号(样式区随之切换目标)。
// **必须在选区放大判定之前**:选区放大模式只改变拖拽语义,
// 不应影响图例的悬停与点选(否则用户在选区放大模式下无法切换信号)。
{
const SignalRef legendHit = LegendAtPoint(x, y);
if (legendHit.IsValid())
{
SelectSignal(legendHit);
m_dragMode = PlotDragMode::NONE;
return true;
}
}
const PlotZoomMode mode = Tab().ZoomMode();
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 double viewMin = Tab().ViewMin();
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)
{
@@ -5337,7 +5353,8 @@ namespace jdi
entry.viewMin == viewMin && entry.viewMax == viewMax && entry.plotX == m_plotRect.X &&
entry.plotWidth == m_plotRect.Width && entry.laneY == lane.rect.Y &&
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;
}
@@ -5362,6 +5379,9 @@ namespace jdi
entry.laneHeight = lane.rect.Height;
entry.laneYMin = lane.yMin;
entry.laneYMax = lane.yMax;
entry.gain = gain;
entry.absolute = absolute;
entry.inverted = inverted;
BuildPolyline(resolved, lane, entry.points, entry.breaks, entry.band, entry.bandBreaks,
entry.bandMode, entry.bandSpiky);
@@ -5511,8 +5531,13 @@ namespace jdi
const std::vector<double>& coarseMin = resolved.data->coarseMin;
const std::vector<double>& coarseMax = resolved.data->coarseMax;
const std::size_t coarseStride = resolved.data->coarseStride;
// ⚠️ 粗粒度分组只存了逐组的 min/max:取绝对值 / 中心反转会把两者互换,
// 此时分组层无法再给出正确的包络,必须回退到逐采样点变换(见 `DrawTransformOrderPreserving`)。
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;
@@ -5566,8 +5591,8 @@ namespace jdi
std::min((i1 - 1) / coarseStride, coarseMin.size() - 1);
for (std::size_t bucket = firstBucket; bucket <= lastBucket; ++bucket)
{
const double bucketLow = coarseMin[bucket];
const double bucketHigh = coarseMax[bucket];
const double bucketLow = coarseMin[bucket] * coarseScale;
const double bucketHigh = coarseMax[bucket] * coarseScale;
if (!std::isfinite(bucketLow))
{
// 该分组全部是无效值:包络到此断开,后续另起一段。
@@ -5590,7 +5615,7 @@ namespace jdi
{
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))
{
// 该列含无效值:包络绘制到本列结束,后续另起一段。
@@ -5624,6 +5649,7 @@ namespace jdi
double boundary = 0.0;
if (PolylineValueAt(time, values, count, i0, t0, boundary))
{
boundary = ApplyDrawTransform(*resolved.style, boundary);
if (boundary < low)
{
low = boundary;
@@ -5637,6 +5663,7 @@ namespace jdi
}
if (PolylineValueAt(time, values, count, i1, t1, boundary))
{
boundary = ApplyDrawTransform(*resolved.style, boundary);
if (boundary < low)
{
low = boundary;
@@ -5722,7 +5749,7 @@ namespace jdi
for (std::size_t index = begin; index < end; index += stride)
{
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))
{
inSegment = false;
@@ -6386,6 +6413,10 @@ namespace jdi
mixFloat(resolved.style->opacity);
mixFloat(resolved.style->lineWidth);
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());
@@ -6431,6 +6462,9 @@ namespace jdi
entry.opacity = resolved.style->opacity;
entry.lineWidth = resolved.style->lineWidth;
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);
}
@@ -8851,7 +8885,7 @@ namespace jdi
int visibleCount = 0;
int drawnCount = 0;
// 先测量图例总宽度,再用半透明底板掠开底下的网格线,提升可读性。
// 先测量图例总宽度:只用来确定装饰缓存矩形(不再绘制任何托底/底板)。
float legendWidth = 0.0f;
for (const SignalRef& ref : laneSignals)
{
@@ -8877,23 +8911,24 @@ namespace jdi
legendWidth += itemWidth;
}
{
// 底板从编号徽标开始一直盖到最后一个图例条目(编号徽标一并被掠开底下的网格线)。
// 底板范围(编号徽标 + 图例条目):这里**只记录装饰矩形**用于缓存贴回。
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 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,
height + 8.0f * m_scale));
}
// 命中装饰缓存:整块图例(底板 + 徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
// ⚠️ **不再绘制不透明托底**(用户要求去掉):原来的底板会把徽标与图例下方的
// 网格线整块擦掉,使这一条看起来像一个"可点击的控件",而它右侧的空白区域
// 实际并不响应点击(选区放大模式下会变成开始框选)。现在图例与徽标直接叠在
// 网格线之上,命中范围与看到的控件一一对应。
//
// 命中装饰缓存:整块图例(徽标 + 色块 + 文字)改由装饰层贴回,跳过重绘。
if (m_useDecorCache)
{
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 徽标使用同一套配色与圆角)。
@@ -9366,6 +9401,8 @@ namespace jdi
{
continue;
}
// 取值读数必须与画出来的波形一致:同样套用增益/取绝对值/中心反转。
value = ApplyDrawTransform(*resolved.style, value);
const float rawY = ValueToY(value, lane);
// 上下越界的数据点不再被丢弃:与左右越界一致,贴边绘制指示方向的三角。
@@ -9388,6 +9425,7 @@ namespace jdi
double otherValue = 0.0;
if (SampleSignalAt(*resolved.file, *resolved.data, otherTime, interpolate, otherValue))
{
otherValue = ApplyDrawTransform(*resolved.style, otherValue);
const double delta = value - otherValue;
// 增量 = 右侧光标取值 − 左侧光标取值,包在圆括号里并按差值显式给出正负号。
text += L" (";
@@ -10318,7 +10356,7 @@ namespace jdi
inSegment = false;
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));
const float x = TimeToX(time[index]);
if (!inSegment)