中心轴曲线配色 + 筛选框交互完善 + 双光标拖拽性能修复

- 中心轴边沿计数的平均值曲线改为「由信号色派生的高对比度颜色」:保持色相、
  大幅拉开明度(与原始波形高对比),透明度与原信号一致;原信号改色时曲线
  颜色随之变化,而光标显示该曲线数值时仍沿用原始信号的配色样式。
- 信号筛选框:内容区左右内缩改为与上下一致(不再「左右空隙明显更大」);
  有输入时右侧显示清空 ✕(悬停高亮 + 手型光标,点击清空且保持编辑态);
  筛选无结果时在列表区提示「无匹配的筛选信号」。
- 光标数值超出上下视界时,三角箭头标识下移 kCursorHandleReserve,
  不再遮挡光标自身的三角把手(含双光标模式)。
- 性能:新增「装饰区域脏标记 + 绘图区局部回送」快路径——绘图区内的悬停/拖拽
  期间跳过标签栏/工具栏/面板/分隔条的重绘,且只 BitBlt 绘图区矩形;
  WM_PAINT 依据更新区域是否落在绘图区内决定快慢路径,窗口缩放/遮挡露出/
  菜单与拾色器弹出等场景仍整帧重绘。
  以 VERY_BIG_DATA.CSV 全信号双光标拖拽实测:单帧耗时由约 28ms 降至约 12ms。
  同时修复拖拽期间残留的信号悬停提示气泡。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-09 19:10:43 +08:00
1 parent fe72afc83c
commit 8ae8d7af4f
6 files changed
+333 -45

No files matched your search

+21 -1
View File
@@ -293,6 +293,14 @@ namespace jdi
*/
bool HitTest(int x, int y) const;
/**
* @brief 查询该位置应使用的鼠标光标(当前仅「筛选信号」清空按钮 ✕ 使用手型)。
* @param [IN] x 客户区横坐标。
* @param [IN] y 客户区纵坐标。
* @return 需要特殊光标时返回光标句柄,否则返回 nullptr。
*/
HCURSOR QueryCursor(int x, int y) const;
/**
* @brief 将列表滚动到指定信号所在位置。
* @param [IN] ref 目标信号引用。
@@ -347,7 +355,8 @@ namespace jdi
OPACITY_VALUE, /**< 不透明度数值框(可点击输入)。 */
WIDTH_SLIDER, /**< 线宽滑块。 */
WIDTH_VALUE, /**< 线宽数值框(可点击输入)。 */
TOGGLE_ALL /**< 全部显示/隐藏按钮。 */
TOGGLE_ALL, /**< 全部显示/隐藏按钮。 */
FILTER_CLEAR /**< 「筛选信号」输入框右侧的清空 ✕。 */
};
/**
@@ -530,6 +539,17 @@ namespace jdi
*/
gd::RectF FilterFieldRect() const;
/**
* @brief 计算「筛选信号」输入框右侧清空按钮 ✕ 的矩形(无输入时为空矩形)。
* @return 清空按钮矩形。
*/
gd::RectF FilterClearRect() const;
/**
* @brief 清空筛选内容(点击 ✕ 时调用),并刷新列表。
*/
void ClearFilter();
/**
* @brief 计算指定行中名称区域的矩形(列表行内联编辑使用)。
* @param [IN] row 行下标。
+106 -15
View File
@@ -28,6 +28,7 @@ namespace jdi
const float kGroupSeparatorWidth = 1.0f; /**< 分组标签右侧分隔线宽度。 */
const float kFilterHeight = 26.0f; /**< 「筛选信号」输入框高度。 */
const float kFilterPadV = 5.0f; /**< 筛选框上下留白。 */
const float kFilterTextPad = 4.0f; /**< 筛选框内文字/输入框相对外框的内缩(四边一致)。 */
/**
* @brief 大小写无关的通配符匹配(`*` 匹配任意长度的任意字符序列)。
@@ -218,6 +219,36 @@ namespace jdi
return UiPointInRect(m_filterRect, static_cast<float>(x), static_cast<float>(y));
}
void SignalPanel::ClearFilter()
{
if (m_filterText.empty() && !m_filterEditing)
{
return;
}
m_filterText.clear();
if (m_filterEditing)
{
// 编辑态下一并重置输入模型,避免残留旧文本(Begin 会整段选中/清空)。
m_filterEdit.Begin(std::wstring(), false, EditFilter::ANY, 0u);
}
// 清空后回到完整列表:重建行集合、回到顶部。
m_rowsDirty = true;
m_scrollOffset = 0.0f;
m_layoutDirty = true;
}
HCURSOR SignalPanel::QueryCursor(int x, int y) const
{
// 清空按钮 ✕ 是独立可点击控件,用手型光标给出除高亮之外的额外交互提示。
const gd::RectF clearRect = FilterClearRect();
if (clearRect.Width > 0.0f &&
UiPointInRect(clearRect, static_cast<float>(x), static_cast<float>(y)))
{
return LoadCursorW(nullptr, IDC_HAND);
}
return nullptr;
}
void SignalPanel::BeginFilterEdit()
{
if (m_filterEditing)
@@ -723,25 +754,47 @@ namespace jdi
UiDrawRoundRect(g, m_filterRect, kCorner * m_scale,
m_filterEditing ? m_theme->accent : m_theme->border,
m_filterEditing ? 1.6f : 1.0f);
const float pad = kFilterTextPad * m_scale;
const gd::RectF clearRect = FilterClearRect();
// 有输入内容时在右侧留出 ✕ 的位置,文本不会压到按钮上。
const float textRight = (clearRect.Width > 0.0f) ? clearRect.X : (m_filterRect.X + m_filterRect.Width);
const float textWidth = std::max(8.0f, textRight - pad - (m_filterRect.X + pad));
const gd::RectF textRect(m_filterRect.X + pad, m_filterRect.Y, textWidth,
m_filterRect.Height);
if (m_filterEditing)
{
UiDrawEditField(g, FilterFieldRect(), m_filterEdit, m_fonts->uiSmall, m_scale);
gd::RectF fieldRect = FilterFieldRect();
fieldRect.Width = std::max(8.0f, textWidth + pad);
UiDrawEditField(g, fieldRect, m_filterEdit, m_fonts->uiSmall, m_scale);
}
else if (m_filterText.empty())
{
UiText(g, L"筛选信号", m_fonts->uiSmall,
gd::RectF(m_filterRect.X + 8.0f * m_scale, m_filterRect.Y,
m_filterRect.Width - 16.0f * m_scale, m_filterRect.Height),
m_theme->textMuted, TextAlign::LEFT);
UiText(g, L"筛选信号", m_fonts->uiSmall, textRect, m_theme->textMuted, TextAlign::LEFT);
}
else
{
UiText(g, UiEllipsize(g, m_filterText, m_fonts->uiSmall,
m_filterRect.Width - 16.0f * m_scale),
m_fonts->uiSmall,
gd::RectF(m_filterRect.X + 8.0f * m_scale, m_filterRect.Y,
m_filterRect.Width - 16.0f * m_scale, m_filterRect.Height),
m_theme->text, TextAlign::LEFT);
UiText(g, UiEllipsize(g, m_filterText, m_fonts->uiSmall, textWidth), m_fonts->uiSmall,
textRect, m_theme->text, TextAlign::LEFT);
}
// 清空按钮 ✕:仅在筛选框有内容时出现,悬停高亮(鼠标光标保持箭头)。
if (clearRect.Width > 0.0f)
{
const bool clearHot = (m_hoverStyle == StyleHit::FILTER_CLEAR);
if (clearHot)
{
UiFillRoundRect(g, clearRect, 3.0f * m_scale, WithAlpha(m_theme->danger, 0x33));
}
const std::uint32_t crossColor = clearHot ? m_theme->danger : m_theme->textMuted;
const float crossPad = clearRect.Width * 0.28f;
UiDrawLine(g, clearRect.X + crossPad, clearRect.Y + crossPad,
clearRect.X + clearRect.Width - crossPad,
clearRect.Y + clearRect.Height - crossPad, crossColor, 1.5f * m_scale);
UiDrawLine(g, clearRect.X + clearRect.Width - crossPad, clearRect.Y + crossPad,
clearRect.X + crossPad, clearRect.Y + clearRect.Height - crossPad, crossColor,
1.5f * m_scale);
}
}
@@ -755,6 +808,12 @@ namespace jdi
UiText(g, L"尚未加载数据,请打开 CSV 文件", m_fonts->uiSmall, m_listRect, m_theme->textMuted,
TextAlign::CENTER);
}
else if (m_rows.empty() && !m_filterText.empty())
{
// 筛选结果为空:明确提示"无匹配",而不是让人误以为数据丢失。
UiText(g, L"无匹配的筛选信号", m_fonts->uiSmall, m_listRect, m_theme->textMuted,
TextAlign::CENTER);
}
else
{
const int rowCount = static_cast<int>(m_rows.size());
@@ -1393,11 +1452,23 @@ namespace jdi
gd::RectF SignalPanel::FilterFieldRect() const
{
// 输入框内容区:与占位提示/显示文本使用同一内缩,保证编辑时不跳动。
const float inset = 7.0f * m_scale;
return gd::RectF(m_filterRect.X + inset, m_filterRect.Y + 2.0f * m_scale,
// 输入框内容区:左右与上下使用**同一内缩**,避免编辑态出现"左右空隙明显更大"的观感;
// 占位提示与显示文本也用同一内缩,因此进入/退出编辑时文字不会跳动。
const float inset = kFilterTextPad * m_scale;
return gd::RectF(m_filterRect.X + inset, m_filterRect.Y + inset,
std::max(8.0f, m_filterRect.Width - inset * 2.0f),
std::max(8.0f, m_filterRect.Height - 4.0f * m_scale));
std::max(8.0f, m_filterRect.Height - inset * 2.0f));
}
gd::RectF SignalPanel::FilterClearRect() const
{
if (m_filterText.empty())
{
return gd::RectF();
}
const float size = std::max(12.0f, m_filterRect.Height - kFilterTextPad * 2.0f * m_scale);
const float right = m_filterRect.X + m_filterRect.Width - kFilterTextPad * m_scale;
return gd::RectF(right - size, m_filterRect.Y + (m_filterRect.Height - size) * 0.5f, size, size);
}
gd::RectF SignalPanel::EditFieldRect() const
@@ -1610,6 +1681,15 @@ namespace jdi
{
if (m_filterEditing)
{
// 编辑态下 ✕ 位于输入框内容区之内,必须优先于插入符定位处理,
// 否则点击会被当成"把光标移到此处"而清空不了内容。
const gd::RectF clearRect = FilterClearRect();
if (clearRect.Width > 0.0f &&
UiPointInRect(clearRect, static_cast<float>(x), static_cast<float>(y)))
{
ClearFilter();
return true;
}
return UiEditFieldMouseDown(m_fonts->uiSmall, FilterFieldRect(), m_filterEdit, m_scale, x, y);
}
if (m_editField == StyleEditField::NONE)
@@ -1786,6 +1866,11 @@ namespace jdi
{
hoverStyle = StyleHit::TOGGLE_ALL;
}
else if (UiPointInRect(FilterClearRect(), fx, fy))
{
// 筛选框右侧的清空 ✕(仅在筛选框有内容时存在)。
hoverStyle = StyleHit::FILTER_CLEAR;
}
else if (m_tab != nullptr && StyleOf(SelectedRef()) != nullptr)
{
for (int index = 0; index < m_colorCount; ++index)
@@ -1859,6 +1944,12 @@ namespace jdi
// 「筛选信号」输入框:单击进入编辑;点击其它位置则先结束筛选编辑(文本已实时生效)。
if (IsOnFilterBox(x, y))
{
// 右侧 ✕:清空筛选内容(不退出编辑态,便于继续输入)。
if (UiPointInRect(FilterClearRect(), fx, fy))
{
ClearFilter();
return true;
}
BeginFilterEdit();
return true;
}