修复输入框全选高亮溢出、标签页重命名无法失焦、新增信号行删除按钮
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:
1 parent
18b8f3e38e
commit
a1e30985b8
7 files changed
+270
-11
No files matched your search
@@ -220,6 +220,26 @@ namespace jdi
|
||||
*/
|
||||
bool RemoveFile(int fileIndex);
|
||||
|
||||
/**
|
||||
* @brief 从某个分组中移除一条信号,并立即释放它的采样内存。
|
||||
*
|
||||
* 该信号之后的所有信号下标整体前移一位,因此调用方必须紧接着对每个标签页调用
|
||||
* `TabConfig::RemapAfterSignalRemoval()`,并用 `RemapSelectionAfterSignalRemoval()`
|
||||
* 修正共享选中项。使用者一旦确认不再需要该信号即可调用,本调用后其采样缓冲立即析构。
|
||||
*
|
||||
* @param [IN] fileIndex 信号所属文件下标。
|
||||
* @param [IN] signalIndex 文件内信号下标。
|
||||
* @return 移除成功返回 true。
|
||||
*/
|
||||
bool RemoveSignal(int fileIndex, int signalIndex);
|
||||
|
||||
/**
|
||||
* @brief 在数据层移除某条信号之前,按旧下标修正选中引用。
|
||||
* @param [IN] fileIndex 信号所属文件下标。
|
||||
* @param [IN] signalIndex 即将被移除的信号下标。
|
||||
*/
|
||||
void RemapSelectionAfterSignalRemoval(int fileIndex, int signalIndex);
|
||||
|
||||
/**
|
||||
* @brief 判断数据集合是否为空。
|
||||
* @return 未加载任何文件返回 true。
|
||||
@@ -486,6 +506,19 @@ namespace jdi
|
||||
*/
|
||||
void RemapAfterFileRemoval(int fileIndex);
|
||||
|
||||
/**
|
||||
* @brief 在数据层移除某条信号之前,按旧下标修正本配置(不重建网格)。
|
||||
*
|
||||
* 会删除该信号的可见性记录与网格归属,并把同一文件内位于其后的引用下标前移一位。
|
||||
* 若某个网格因此变空则一并移除(与“取消勾选后空网格消失”的策略一致)。
|
||||
*
|
||||
* @param [IN] fileIndex 信号所属文件下标。
|
||||
* @param [IN] signalIndex 即将被移除的信号下标。
|
||||
* @note 调用顺序必须为:各标签页 RemapAfterSignalRemoval → 数据层修正选中 →
|
||||
* 数据层 RemoveSignal → 各标签页 SyncToDataSet。
|
||||
*/
|
||||
void RemapAfterSignalRemoval(int fileIndex, int signalIndex);
|
||||
|
||||
/**
|
||||
* @brief 判断指定信号是否可见。
|
||||
* @param [IN] ref 信号引用。
|
||||
|
||||
@@ -690,6 +690,50 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataSet::RemoveSignal(int fileIndex, int signalIndex)
|
||||
{
|
||||
if (fileIndex < 0 || fileIndex >= static_cast<int>(m_files.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DataFile& file = m_files[static_cast<std::size_t>(fileIndex)];
|
||||
if (signalIndex < 0 || signalIndex >= static_cast<int>(file.signals.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 与 RemoveFile 同一思路:先把待移除项移到末位(move 赋值时其原有缓冲当场析构),
|
||||
// 再 pop_back + shrink_to_fit,使该信号的全部采样内存在本调用内立即归还给系统,
|
||||
// 而不是留在容量里等后续插入覆盖。
|
||||
std::vector<SignalData>& signals = file.signals;
|
||||
const std::size_t position = static_cast<std::size_t>(signalIndex);
|
||||
for (std::size_t index = position; index + 1 < signals.size(); ++index)
|
||||
{
|
||||
signals[index] = std::move(signals[index + 1]);
|
||||
}
|
||||
signals.pop_back();
|
||||
signals.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataSet::RemapSelectionAfterSignalRemoval(int fileIndex, int signalIndex)
|
||||
{
|
||||
if (fileIndex < 0 || signalIndex < 0 || m_selected.fileIndex != fileIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_selected.signalIndex == signalIndex)
|
||||
{
|
||||
// 选中的信号本身被移除:取消选中。
|
||||
m_selected = SignalRef();
|
||||
}
|
||||
else if (m_selected.signalIndex > signalIndex)
|
||||
{
|
||||
// 其后信号整体前移一位。
|
||||
--m_selected.signalIndex;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t DataSet::SignalCount() const
|
||||
{
|
||||
std::size_t count = 0;
|
||||
@@ -1112,6 +1156,49 @@ namespace jdi
|
||||
m_lanes.end());
|
||||
}
|
||||
|
||||
void TabConfig::RemapAfterSignalRemoval(int fileIndex, int signalIndex)
|
||||
{
|
||||
if (fileIndex < 0 || signalIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 可见性表:删掉该信号对应的那一列。
|
||||
const std::size_t slot = static_cast<std::size_t>(fileIndex);
|
||||
if (slot < m_visible.size())
|
||||
{
|
||||
std::vector<bool>& fileVisible = m_visible[slot];
|
||||
if (static_cast<std::size_t>(signalIndex) < fileVisible.size())
|
||||
{
|
||||
fileVisible.erase(fileVisible.begin() + signalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 网格归属:删掉该信号本身,并把同一文件内位于其后的引用下标前移一位。
|
||||
for (Lane& lane : m_lanes)
|
||||
{
|
||||
std::vector<SignalRef>& refs = lane.signals;
|
||||
refs.erase(std::remove_if(refs.begin(), refs.end(),
|
||||
[fileIndex, signalIndex](const SignalRef& ref)
|
||||
{
|
||||
return ref.fileIndex == fileIndex &&
|
||||
ref.signalIndex == signalIndex;
|
||||
}),
|
||||
refs.end());
|
||||
for (SignalRef& ref : refs)
|
||||
{
|
||||
if (ref.fileIndex == fileIndex && ref.signalIndex > signalIndex)
|
||||
{
|
||||
--ref.signalIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 被移除信号若独占了某个网格,该网格随之消失(与取消勾选的行为一致)。
|
||||
m_lanes.erase(std::remove_if(m_lanes.begin(), m_lanes.end(),
|
||||
[](const Lane& lane) { return lane.signals.empty(); }),
|
||||
m_lanes.end());
|
||||
}
|
||||
|
||||
bool TabConfig::IsVisible(const SignalRef& ref) const
|
||||
{
|
||||
if (ref.fileIndex < 0 || static_cast<std::size_t>(ref.fileIndex) >= m_visible.size())
|
||||
|
||||
@@ -578,12 +578,20 @@ namespace jdi
|
||||
const int saveState = g.Save();
|
||||
g.SetClip(clipRect, gd::CombineModeIntersect);
|
||||
|
||||
// 选区高亮绘制在文本之下,整段选中时呈现“已全选”观感;
|
||||
// 叠加上 GDI+ 裁剪,高亮同样不会越过输入框边界。
|
||||
// 选区高亮绘制在文本之下,整段选中时呈现“已全选”观感。
|
||||
// ⚠️ 这里**必须自己把矩形夹到内容区**:`UiFillRect` 对面积足够大的不透明矩形会走
|
||||
// GDI `FillRect` 快路径,而 GDI 绘制**不受 GDI+ 裁剪区约束**,长文本的全选高亮
|
||||
// (宽度等于整段文本宽度)因此会溢出到输入框之外。夹取之后矩形始终落在框内,
|
||||
// 既保留快路径又不越界。
|
||||
if (edit.HasSelection() && selEndX > selBeginX)
|
||||
{
|
||||
const gd::RectF highlight(textLeft + selBeginX, rect.Y + 3.0f * scale, selEndX - selBeginX,
|
||||
rect.Height - 6.0f * scale);
|
||||
gd::RectF highlight(textLeft + selBeginX, rect.Y + 3.0f * scale, selEndX - selBeginX,
|
||||
rect.Height - 6.0f * scale);
|
||||
const float left = std::max(highlight.X, clipRect.X);
|
||||
const float right = std::min(highlight.X + highlight.Width, clipRect.X + clipRect.Width);
|
||||
const float top = std::max(highlight.Y, clipRect.Y);
|
||||
const float bottom = std::min(highlight.Y + highlight.Height, clipRect.Y + clipRect.Height);
|
||||
highlight = gd::RectF(left, top, right - left, bottom - top);
|
||||
UiFillRect(g, highlight, theme.accent);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user