修复输入框全选高亮溢出、标签页重命名无法失焦、新增信号行删除按钮

1. 内联输入框全选高亮越界(组名/标签页名/信号名编辑)
   UiFillRect 在不透明且面积 >= 20000 时走 GDI FillRect 快路径,而 GDI 填充不受
   GDI+ SetClip 约束;长文本全选时高亮宽度可达上千像素,于是画到输入框之外。
   UiDrawEditField 现在先把高亮矩形与裁剪区求交再填充。

2. 标签页重命名处于编辑态时,点击输入框之外的任何位置都不退出编辑
   Application 只在点击落在标签栏内时才把点击交给 TabBar。现在 WM_LBUTTONDOWN
   会先检查 m_tabBar.IsEditing(),是则提交(OnEditKey + OnTabBarResult)并退出编辑态,
   覆盖绘图区、信号面板、工具栏、分隔条等位置,且不会丢失新名字。

3. 信号选择区域每个信号新增 ✕ 删除按钮
   - 位置与所在组复选框同一列(垂直对齐),悬停显示手型光标并高亮。
   - 点击后面板向宿主发送 WM_APP+2;Application::RemoveSignal 会重映射所有标签页的
     可见性与网格引用、修正当前选中项,再真正从 DataSet 中删除该信号并释放采样内存;
     删除组内最后一个信号时退化为移除整个文件分组。
   - DataSet::RemoveSignal 采用移动填充 + shrink_to_fit,保证缓冲区内即刻析构。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-10 10:54:50 +08:00
1 parent 18b8f3e38e
commit a1e30985b8
7 files changed
+270 -11

No files matched your search

+60 -4
View File
@@ -29,6 +29,7 @@ namespace jdi
const float kFilterHeight = 26.0f; /**< 「筛选信号」输入框高度。 */
const float kFilterPadV = 5.0f; /**< 筛选框上下留白。 */
const float kFilterTextPad = 4.0f; /**< 筛选框内文字/输入框相对外框的内缩(四边一致)。 */
const float kRemoveButtonSize = 14.0f; /**< 信号行「移除该信号」✕ 按钮尺寸。 */
/**
* @brief 大小写无关的通配符匹配(`*` 匹配任意长度的任意字符序列)。
@@ -237,7 +238,7 @@ namespace jdi
m_layoutDirty = true;
}
HCURSOR SignalPanel::QueryCursor(int x, int y) const
HCURSOR SignalPanel::QueryCursor(int x, int y)
{
// 清空按钮 ✕ 是独立可点击控件,用手型光标给出除高亮之外的额外交互提示。
const gd::RectF clearRect = FilterClearRect();
@@ -246,6 +247,13 @@ namespace jdi
{
return LoadCursorW(nullptr, IDC_HAND);
}
// 信号行左侧的「移除该信号」✕ 同理。
RowPart part = RowPart::NONE;
if (HitRow(x, y, part) >= 0 && part == RowPart::REMOVE_SIGNAL)
{
return LoadCursorW(nullptr, IDC_HAND);
}
return nullptr;
}
@@ -457,6 +465,23 @@ namespace jdi
rect.Y + (rect.Height - height) * 0.5f, width, height);
}
gd::RectF SignalPanel::SignalRemoveRect(int row) const
{
if (row < 0 || row >= static_cast<int>(m_rows.size()) ||
m_rows[static_cast<std::size_t>(row)].kind != RowKind::SIGNAL)
{
return gd::RectF();
}
// 分组行的复选框矩形为 [rect.X + kPadding + kDisclosureWidth, +kCheckSize],
// 信号行的折叠三角列是空着的,正好用来放 ✕:左边界与分组复选框对齐
// (垂直方向同列),右边界仍留在三角列内,不会压到信号自己的复选框。
const gd::RectF rect = RowRect(row);
const float left = rect.X + (kPadding + kDisclosureWidth) * m_scale;
const float size = kRemoveButtonSize * m_scale;
return gd::RectF(left, rect.Y + (rect.Height - size) * 0.5f, size, size);
}
int SignalPanel::HitRow(int x, int y, RowPart& part)
{
part = RowPart::NONE;
@@ -524,7 +549,12 @@ namespace jdi
const SignalRef ref{row.fileIndex, row.signalIndex};
const bool badgeVisible = (m_tab != nullptr) && m_tab->IsVisible(ref);
if (UiPointInRect(checkRect, fx, fy))
// ✕ 命中优先于复选框:两者紧邻,顺序保证不会互相抢命中。
if (UiPointInRect(SignalRemoveRect(rowIndex), fx, fy))
{
part = RowPart::REMOVE_SIGNAL;
}
else if (UiPointInRect(checkRect, fx, fy))
{
part = RowPart::CHECK;
}
@@ -980,6 +1010,18 @@ namespace jdi
const bool visible = m_tab->IsVisible(ref);
UiDrawCheckBox(g, checkRect, visible ? 1 : 0, 3.0f * m_scale);
// 「移除该信号」✕:位于复选框前方(分组复选框所在列),悬停时高亮为危险色。
{
const gd::RectF removeRect = SignalRemoveRect(row);
const bool removeHot = hovered && (m_hoverPart == RowPart::REMOVE_SIGNAL);
if (removeHot)
{
UiFillRoundRect(g, removeRect, 3.0f * m_scale, m_theme->pressed);
}
UiDrawCrossIcon(g, removeRect, removeHot ? m_theme->danger : m_theme->textMuted,
1.4f * m_scale);
}
// 颜色块:叠加不透明度,透明度过低时自动补描边(与样式区色块规则一致)。
const gd::RectF swatchRect(checkRect.X + checkRect.Width + 6.0f * m_scale,
centerY - kSwatchSize * m_scale * 0.5f, kSwatchSize * m_scale,
@@ -2082,6 +2124,20 @@ namespace jdi
}
const SignalRef ref{item.fileIndex, item.signalIndex};
// ✕:用户明确表示不再需要该信号 → 交由宿主从数据层移除并释放其采样内存。
// 必须在 SelectSignal 之前处理,避免选中一条即将被删除的信号。
if (part == RowPart::REMOVE_SIGNAL)
{
if (m_host != nullptr)
{
// wParam = 文件下标,lParam = 文件内信号下标(见 Application 的消息处理)。
PostMessageW(m_host, WM_APP + 2, static_cast<WPARAM>(item.fileIndex),
static_cast<LPARAM>(item.signalIndex));
}
return true;
}
SelectSignal(ref);
if (part == RowPart::CHECK)
@@ -2202,8 +2258,8 @@ namespace jdi
return BeginEdit(StyleEditField::FILE_NAME, row);
}
// 复选框与网格徽标各自由单击处理,双击不再切换可见性。
if (part == RowPart::CHECK || part == RowPart::LANE_BADGE)
// 复选框、网格徽标与「移除信号」✕ 各自由单击处理,双击不再切换可见性/进入重命名。
if (part == RowPart::CHECK || part == RowPart::LANE_BADGE || part == RowPart::REMOVE_SIGNAL)
{
return false;
}