fix(ui): 采样统计显示一致化、GX 拖拽在布局边界下可用、信号「移动到…」与拖拽排序
一、采样间隔/频率显示不一致(用户报告 1ms vs 1000us、100us 0ns) * FormatDurationCompound 改为「先量化到整数皮秒、再整数分解」, 彻底消除同一数值因浮点噪声落到不同单位/留下零尾数的问题; 秒级分支与亚秒分支共用 totalPs 方案,次级进位/单位升级集中在一处。 * FormatFrequency 的单位选择改为按「舍入后的显示值」判定(999.9999999Hz 与 1000Hz 均为 1.000kHz),并做向下校正避免 0.001kHz。 二、选区截取面板 * 提示文本改为用户指定原文「启用双光标模式以进行选区截取」。 * EdgePanelWidth 计入该提示的实际文本宽度;绘制改用 UiTextClipped 不再省略。 * viewHeight 补回漏减的 bottomPad,修掉条目区与按钮之间的空白。 三、未展开的信号条目栏悬停不再变成手型光标。 四、开启「显示布局边界」后 GX 标签可正常拖拽排序 * 抽出 PaintLaneReorderOverlay(吸附槽位 + 被拖轮廓),贴图与整幅重绘共用。 * 整幅重绘在 LANE_REORDER 时按 LaneDragOffset 逐格平移重画(被拖网格最后画), 修掉调试模式下不跟随鼠标、无吸附提示、只能盲拖的问题。 五、信号右键新增「移动到…」二级菜单(位于「仅显示该信号」之上) * 条目为「分组 N(组名)」,当前组打勾;无标题行;底部「新建分组」。 * MenuItem::submenuMaxRows + PopupMenu 独立子菜单滚动(滚动条、滚轮、视口命中过滤), IsScrollable 计入子菜单可滚状态。 * 数据层 DataSet::MoveSignalToGroup/MoveSignal 让时间轴随信号搬运 (独占则移动、共享则复制);TabConfig::RemapAfterSignalMove 改写引用而非先删后插。 * Application::MoveSignalCore 统一入口,保证派生计算暂停、引用修正、选中迁移与同步一致。 六、信号列表支持按住拖拽排序(组内重排 / 跨组移动) * 4px 阈值进入拖拽;条目脱离原位跟随鼠标(阴影 + 强调色描边 + 插入指示线)。 * 其它信号行/分组行按插入位置做避让位移动画(RowRect 统一叠加插值位移, 宿主 kScrollTimerId 驱动 AdvanceSignalDragAnimation)。 * 数据层 DataSet::MoveSignal 支持任意位置;TabConfig::RemapAfterSignalReorder 线性重映射。 * 拖拽中禁用组名悬浮;双击改名与 InvalidateRows 均正确终止拖拽态。 验证:删 .o 全量重编译零 error / 零 warning;产物 873,984 B(< 1MB 预警阈值)。 本轮未使用鼠标/键盘/屏幕,未运行任何 GUI 探针,仅编译验证 + 源码级核对。
This commit is contained in:
1 parent
21a9865365
commit
97b361dedd
23 files changed
+1581
-74
No files matched your search
@@ -244,8 +244,186 @@ namespace jdi
|
||||
SetStatusMessage(L"已从工作区移除信号:" + name, false);
|
||||
}
|
||||
|
||||
void Application::UpdateWindowTitle()
|
||||
std::wstring Application::UniqueGroupName(const std::wstring& base) const
|
||||
{
|
||||
const auto taken = [this](const std::wstring& candidate)
|
||||
{
|
||||
for (const SignalGroup& group : m_dataSet.Groups())
|
||||
{
|
||||
if (group.name == candidate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!taken(base))
|
||||
{
|
||||
return base;
|
||||
}
|
||||
for (int suffix = 2; suffix < 1000; ++suffix)
|
||||
{
|
||||
const std::wstring candidate = base + L" (" + std::to_wstring(suffix) + L")";
|
||||
if (!taken(candidate))
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
void Application::MoveSignalCore(int fromGroup, int fromSignal, int toGroup, int toIndex)
|
||||
{
|
||||
const SignalGroup* sourceGroup = m_dataSet.GroupAt(fromGroup);
|
||||
const SignalGroup* targetGroup = m_dataSet.GroupAt(toGroup);
|
||||
if (sourceGroup == nullptr || targetGroup == nullptr || fromSignal < 0 ||
|
||||
static_cast<std::size_t>(fromSignal) >= sourceGroup->signals.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool sameGroup = (fromGroup == toGroup);
|
||||
|
||||
// 目标下标:负数/越界表示追加到末尾(组内重排时"末尾"= 组内信号数-1,因为该信号会先被摘出)。
|
||||
int position = toIndex;
|
||||
const int targetCount = static_cast<int>(targetGroup->signals.size());
|
||||
if (sameGroup)
|
||||
{
|
||||
if (position < 0 || position >= targetCount)
|
||||
{
|
||||
position = targetCount - 1;
|
||||
}
|
||||
if (position == fromSignal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (position < 0 || position > targetCount)
|
||||
{
|
||||
position = targetCount;
|
||||
}
|
||||
|
||||
SignalRef from;
|
||||
from.groupIndex = fromGroup;
|
||||
from.signalIndex = fromSignal;
|
||||
SignalRef to;
|
||||
to.groupIndex = toGroup;
|
||||
to.signalIndex = position;
|
||||
|
||||
const bool visible = m_tabs.empty() ? false : m_tabs.front().IsVisible(from);
|
||||
|
||||
// 移动会搬运大量采样缓冲:先暂停后台派生曲线计算,避免其读取正在被移动的内存。
|
||||
m_plot.PauseDerivedAsync();
|
||||
|
||||
// 跨组且目标分组是多标签页里"新出现"的(例如刚建的分组)时,先补齐可见性表列,
|
||||
// 否则 `RemapAfterSignalMove` 无法在新组里插入"沿用源可见性"的那一列,
|
||||
// 移动过去后信号会变成未勾选、曲线直接消失。
|
||||
if (!sameGroup)
|
||||
{
|
||||
for (TabConfig& tab : m_tabs)
|
||||
{
|
||||
tab.SyncToDataSet(m_dataSet);
|
||||
}
|
||||
for (TabConfig& tab : m_tabs)
|
||||
{
|
||||
tab.RemapAfterSignalMove(from, to, tab.IsVisible(from));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TabConfig& tab : m_tabs)
|
||||
{
|
||||
tab.RemapAfterSignalReorder(fromGroup, fromSignal, position);
|
||||
}
|
||||
}
|
||||
(void)visible;
|
||||
|
||||
// 共享选中项:被移动的正是当前选中信号时,改为指向新位置。
|
||||
if (m_dataSet.Selected().groupIndex == fromGroup)
|
||||
{
|
||||
if (sameGroup)
|
||||
{
|
||||
// 与 `RemapAfterSignalReorder` 同一套线性映射。
|
||||
int sel = m_dataSet.Selected().signalIndex;
|
||||
if (sel == fromSignal)
|
||||
{
|
||||
sel = position;
|
||||
}
|
||||
else if (fromSignal < position)
|
||||
{
|
||||
if (sel > fromSignal && sel <= position)
|
||||
{
|
||||
--sel;
|
||||
}
|
||||
}
|
||||
else if (sel >= position && sel < fromSignal)
|
||||
{
|
||||
++sel;
|
||||
}
|
||||
SignalRef next;
|
||||
next.groupIndex = fromGroup;
|
||||
next.signalIndex = sel;
|
||||
m_dataSet.SetSelected(next);
|
||||
}
|
||||
else if (m_dataSet.Selected().signalIndex == fromSignal)
|
||||
{
|
||||
m_dataSet.SetSelected(to);
|
||||
}
|
||||
}
|
||||
|
||||
int newSignalIndex = -1;
|
||||
// 参数已在此前校验过(分组与信号下标都合法),本调用不会失败。
|
||||
m_dataSet.MoveSignal(fromGroup, fromSignal, toGroup, position, newSignalIndex);
|
||||
|
||||
SyncTabsToDataSet();
|
||||
}
|
||||
|
||||
void Application::ReorderSignal(int fromGroup, int fromSignal, int toGroup, int toIndex)
|
||||
{
|
||||
MoveSignalCore(fromGroup, fromSignal, toGroup, toIndex);
|
||||
}
|
||||
|
||||
void Application::MoveSignalToGroup(int fromGroup, int fromSignal, int toGroup)
|
||||
{
|
||||
const SignalGroup* sourceGroup = m_dataSet.GroupAt(fromGroup);
|
||||
if (sourceGroup == nullptr || fromSignal < 0 ||
|
||||
static_cast<std::size_t>(fromSignal) >= sourceGroup->signals.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const std::wstring signalName = sourceGroup->signals[static_cast<std::size_t>(fromSignal)].name;
|
||||
|
||||
// 「新建分组」:先追加一个空分组(名字不重复),再按普通跨组移动处理。
|
||||
int targetGroup = toGroup;
|
||||
if (targetGroup < 0)
|
||||
{
|
||||
SignalGroup group;
|
||||
group.name = UniqueGroupName(L"新建分组");
|
||||
if (!m_dataSet.AddGroup(std::move(group), targetGroup))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (targetGroup == fromGroup)
|
||||
{
|
||||
return; // 已经在该分组,无需移动
|
||||
}
|
||||
const SignalGroup* targetGroupData = m_dataSet.GroupAt(targetGroup);
|
||||
if (targetGroupData == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MoveSignalCore(fromGroup, fromSignal, targetGroup,
|
||||
static_cast<int>(targetGroupData->signals.size()));
|
||||
|
||||
const SignalGroup* destination = m_dataSet.GroupAt(targetGroup);
|
||||
SetStatusMessage(L"已将信号「" + signalName + L"」移动到分组:" +
|
||||
(destination != nullptr ? destination->name : std::wstring()),
|
||||
false);
|
||||
}
|
||||
|
||||
void Application::UpdateWindowTitle() {
|
||||
// 标题栏 = 应用名称 + 分隔竖线 + 内存占用(单位在 KB/MB/GB 间自适应,保留两位小数)。
|
||||
// 除内存占用外不再附带标签页、文件数量等无关信息。
|
||||
// ⚠️ 内存口径 = **私有工作集**(与任务管理器"内存"列一致),不是总工作集。
|
||||
|
||||
Reference in new issue
Block a user