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

标题栏:
- 应用名称后追加「  |  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

+190 -33
View File
@@ -88,15 +88,15 @@ namespace jdi
return p == pattern.size();
}
// 样式编辑区:固定四行网格(颜色 / 样式 / 透明度 / 线宽),行高、间距与内容列宽度统一,
// 保证面板宽度变化时四行控件始终对齐,且不会被无限制拉伸。
// 样式编辑区:固定六行网格(颜色 / 样式 / 线宽 / 透明度 / 系数增益 / 取绝对值与中心反转),
// 行高、间距与内容列宽度统一,保证面板宽度变化时各行控件始终对齐,且不会被无限制拉伸。
const float kStylePadH = 12.0f; /**< 样式区左右内边距。 */
const float kStylePadV = 12.0f; /**< 样式区上下内边距。 */
const float kStyleRowHeight = 26.0f; /**< 样式区单行高度。 */
const float kStyleRowGap = 10.0f; /**< 样式区行间距。 */
const float kStyleLabelWidth = 44.0f; /**< 样式行标签列宽度。 */
const float kStyleLabelWidth = 56.0f; /**< 样式行标签列宽度(需容纳「系数增益」四个字)。 */
const float kStyleLabelGap = 8.0f; /**< 标签列与内容列间距。 */
const float kStyleRowCount = 4.0f; /**< 样式区固定行数。 */
const float kStyleRowCount = 6.0f; /**< 样式区固定行数。 */
const float kStyleContentMax = 260.0f; /**< 内容列最大宽度(再拉宽面板也不再延伸)。 */
const float kSwatchEdge = 18.0f; /**< 颜色色块边长上限(含自定义颜色入口)。 */
const float kSwatchMin = 11.0f; /**< 颜色色块边长下限。 */
@@ -105,10 +105,16 @@ namespace jdi
const float kSliderValueWidth = 52.0f; /**< 滑块行数值文本宽度(透明度与线宽一致)。 */
const float kSliderValueGap = 8.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 kWidthMax = 10.0f; /**< 允许的最大线宽(px)。 */
/** @brief 系数增益允许的绝对值上限(防止输入极端值把波形完全挤出可视区)。 */
const double kGainLimit = 10000.0;
/** @brief 可选绘制样式顺序(不含阶梯方式)。 */
const DrawMode kModeOptions[] = {DrawMode::LINE, DrawMode::SCATTER, DrawMode::LINE_SCATTER};
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);
}
/**
* @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()
@@ -257,6 +290,16 @@ namespace jdi
{
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;
}
@@ -634,8 +677,10 @@ namespace jdi
const gd::RectF& colorRow = m_styleRowRects[0];
const gd::RectF& modeRow = m_styleRowRects[1];
const gd::RectF& opacityRow = m_styleRowRects[2];
const gd::RectF& widthRow = m_styleRowRects[3];
const gd::RectF& widthRow = m_styleRowRects[2];
const gd::RectF& opacityRow = m_styleRowRects[3];
const gd::RectF& gainRow = m_styleRowRects[4];
const gd::RectF& flagRow = m_styleRowRects[5];
// ---- 颜色行:预设色块 + 同尺寸自定义颜色入口(合计恰好铺满内容列宽度)----
int paletteCount = 0;
@@ -689,20 +734,30 @@ namespace jdi
modeWidth, rowHeight);
}
// ---- 透明度行与线宽行:几何完全一致,保证纵向宽度对齐 ----
// ---- 线宽行与透明度行:几何完全一致,保证纵向宽度对齐(线宽在上、透明度在下)----
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 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,
trackWidth, trackHeight);
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, opacityRow.Y,
valueWidth, rowHeight);
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);
// ---- 系数增益行:靠左的数值输入框 ----
const float gainWidth = std::min(kGainFieldWidth * m_scale, contentWidth);
m_gainFieldRect = gd::RectF(contentLeft, gainRow.Y, gainWidth, 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)
@@ -1207,29 +1262,9 @@ namespace jdi
}
}
// ---- 第三行:透明度(数值可点击输入)----
// ---- 第三行:线宽(数值可点击输入)----
{
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"线宽");
paintRowLabel(2, L"线宽");
const double ratio =
(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);
}
}
// ---- 第四行:透明度(数值可点击输入)----
{
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
@@ -1372,6 +1466,16 @@ namespace jdi
initial = std::to_wstring(static_cast<int>(style->opacity * 100.0f + 0.5f));
break;
}
case StyleEditField::GAIN:
{
const SignalStyle* style = StyleOf(SelectedRef());
if (style == nullptr)
{
return false;
}
initial = FormatGainText(style->gain);
break;
}
case StyleEditField::SIGNAL_NAME:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()))
@@ -1476,6 +1580,25 @@ namespace jdi
}
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:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()) || m_dataSetWritable == nullptr)
@@ -1530,6 +1653,8 @@ namespace jdi
return m_widthValueRect;
case StyleEditField::OPACITY:
return m_opacityValueRect;
case StyleEditField::GAIN:
return m_gainFieldRect;
case StyleEditField::SIGNAL_NAME:
case StyleEditField::FILE_NAME:
return RowNameRect(m_editRow);
@@ -1986,6 +2111,18 @@ namespace jdi
{
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 &&
@@ -2126,6 +2263,22 @@ namespace jdi
{
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);
}
if (UiPointInRect(m_gainFieldRect, fx, fy))
{
return BeginEdit(StyleEditField::GAIN);
}
RowPart part = RowPart::NONE;
const int row = HitRow(x, y, part);