修复拆分网格误继承实验性功能 / 阶跃线宽 / 面板拖拽残影 / 自由标记卡顿与交互
- 拆分网格:实验性功能只由原网格的首个信号继承(LaneCarry::owner); SplitLane 只让第一份继承;新增 PruneLaneSignalItems 按 lane.signals 过滤四类 逐信号条目(修复"新网格出现别人的固定点、面板里却查不到") - 阶跃信号:包络带横向膨胀半径 ceil -> floor(1px 线宽不再把竖直沿撑成 3px) - 浮动面板:面板位置移出 PanelGlobalKey(拖动单个面板不再重画全部面板), 新增 vacatedRects 让"上一帧被移动/缩小/关闭的面板腾出的区域"也强制重画 - 自由标记:新增 FreeMarkerContents 内容缓存(键不含视界),大量固定点时拖光标/平移不再逐帧采样; 透明度滑块与光标模式解耦(始终显示);悬停胶囊恢复不透明(瞄着被遮住的数据点时除外); 胶囊与面板五列按实测文本撑开,不再省略 - 面板交互:折起指示器与信号列表组名同款三角;GX 徽标/标题栏悬停不改光标、移除徽标气泡; 标题栏双击滚动到该网格(单击不触发,双击不再折起) - 验证:新增离屏 .preflight/fixcheck 37/37(三处 A/B 均能如实失败); 回归 round3check 31/31、round4check 34/34、round5check 5/5、editcheck 20/20、convcheck 18/18; make release/debug 零警告
This commit is contained in:
1 parent
3400ac7c29
commit
8f735d51be
8 files changed
+654
-198
No files matched your search
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
@@ -1013,6 +1014,17 @@ namespace jdi
|
||||
*/
|
||||
std::vector<SignalRef> freeMarkerCollapsedGroups;
|
||||
|
||||
/**
|
||||
* @brief 「自由标记」面板五列的**实测内容宽度**(设备像素,0 = 还没量过)。
|
||||
*
|
||||
* 用户 2026-10-21 明确要求:面板里的取值 / 枚举文本 / 增量 / 时刻 / 间隔
|
||||
* **不得出现省略号(…)**。因此列宽不能再用固定值:绘制该面板时顺便把每格
|
||||
* 真实文本量一遍,取最大值存到这里;布局(`EdgePanelWidth` / `EdgePanelLayoutFor`)
|
||||
* 用它与缺省列宽 `kFreeMarkerColumnWidths` 的较大者,于是列宽自然贴合内容。
|
||||
* ⚠️ 存的是**设备像素**(已经乘过 DPI 缩放),与字体直接对应。
|
||||
*/
|
||||
std::array<float, 5> freeMarkerColumnWidths{};
|
||||
|
||||
/** 某信号分组在「自由标记」面板里是否被收起。 */
|
||||
bool FreeMarkerGroupCollapsed(const SignalRef& ref) const
|
||||
{
|
||||
|
||||
@@ -61,6 +61,38 @@ namespace jdi
|
||||
PadValueRange(yMin, yMax);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清掉网格里**不属于本网格信号**的逐信号条目(均值滤波 / 中心轴 / 弱峰调整 / 自由标记)。
|
||||
*
|
||||
* 这些条目按信号保存,但只有“该信号就在本网格里”时才有意义。
|
||||
* 网格重建(合并 → 拆分)时上一帧的条目是按网格整体携带的,若不过滤就会出现:
|
||||
* 拆分出来的新网格里仍然画着**其它信号**的固定点(用户 2026-10-21 报告:
|
||||
* 第二个网格上出现“野生的”自由标记点,而浮动面板里又没有任何条目)。
|
||||
*
|
||||
* @param [IN,OUT] lane 目标网格(`signals` 必须已经就位)。
|
||||
*/
|
||||
inline void PruneLaneSignalItems(Lane& lane)
|
||||
{
|
||||
const auto inLane = [&lane](const SignalRef& ref)
|
||||
{
|
||||
return std::find(lane.signals.begin(), lane.signals.end(), ref) != lane.signals.end();
|
||||
};
|
||||
const auto prune = [&inLane](auto& items)
|
||||
{
|
||||
items.erase(std::remove_if(items.begin(), items.end(),
|
||||
[&inLane](const auto& item) { return !inLane(item.ref); }),
|
||||
items.end());
|
||||
};
|
||||
prune(lane.meanFilterItems);
|
||||
prune(lane.edgeCenterItems);
|
||||
prune(lane.weakPeakItems);
|
||||
prune(lane.freeMarkerItems);
|
||||
lane.freeMarkerCollapsedGroups.erase(
|
||||
std::remove_if(lane.freeMarkerCollapsedGroups.begin(), lane.freeMarkerCollapsedGroups.end(),
|
||||
[&inLane](const SignalRef& ref) { return !inLane(ref); }),
|
||||
lane.freeMarkerCollapsedGroups.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 依据全部可见信号重新生成网格划分。
|
||||
* @param [IN] visible 各信号的可见性(按 [分组][信号] 索引)。
|
||||
@@ -74,6 +106,16 @@ namespace jdi
|
||||
struct LaneCarry
|
||||
{
|
||||
bool matched = false; /**< 是否找到该信号原本所在的网格(false = 新出现的信号)。 */
|
||||
/**
|
||||
* @brief 该信号是否是原网格的**首个信号**(= 这个网格的“实验性功能”持有者)。
|
||||
*
|
||||
* ⚠️ 实验性功能(边沿计数 / 中心轴 / 均值滤波 / 弱峰调整 / 自由标记)是**网格级**开关,
|
||||
* 但一个网格可能与其它信号共用(合并网格)。若把“开着”的状态按信号逐个继承,
|
||||
* 那么「合并网格 → 拆分网格」之后**每个**拆出来的网格都会带着这些功能
|
||||
* (用户 2026-10-21 报告:第二个网格也自动启用了实验性功能、并出现了
|
||||
* 其它信号的固定点镜像)。因此只有**首个信号**继承功能状态,其余信号视为默认(关闭)。
|
||||
*/
|
||||
bool owner = false;
|
||||
bool fitYToSelection = false;
|
||||
bool edgeCount = false;
|
||||
double edgeValue = 0.0;
|
||||
@@ -139,9 +181,12 @@ namespace jdi
|
||||
carry.freeMarkerOpacity = lane.freeMarkerOpacity;
|
||||
carry.freeMarkerScroll = lane.freeMarkerScroll;
|
||||
carry.freeMarkerCollapsedGroups = lane.freeMarkerCollapsedGroups;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
for (std::size_t signalIndex = 0; signalIndex < lane.signals.size(); ++signalIndex)
|
||||
{
|
||||
previousCarry.emplace_back(ref, carry);
|
||||
LaneCarry entry = carry;
|
||||
// 只有首个信号是这些网格级开关的持有者(见 `LaneCarry::owner` 说明)。
|
||||
entry.owner = (signalIndex == 0);
|
||||
previousCarry.emplace_back(lane.signals[signalIndex], entry);
|
||||
}
|
||||
}
|
||||
const auto inheritedCarry = [&previousCarry](const SignalRef& ref)
|
||||
@@ -160,6 +205,12 @@ namespace jdi
|
||||
const LaneCarry carry = inheritedCarry(ref);
|
||||
// 新出现的信号(之前没有网格):继承「全局 Y 轴自适应」是否开启。
|
||||
lane.fitYToSelection = carry.matched ? carry.fitYToSelection : previousAllAutoFit;
|
||||
// ⚠️ 实验性功能的状态**只由原网格的首个信号继承**(见 `LaneCarry::owner`):
|
||||
// 否则「合并网格 → 拆分网格」会把一个网格的功能复制到全部拆分结果上。
|
||||
if (!carry.matched || !carry.owner)
|
||||
{
|
||||
return; // 其它信号:保持 `Lane` 的默认值(全部功能关闭、条目为空)。
|
||||
}
|
||||
lane.edgeCount = carry.edgeCount;
|
||||
lane.edgeValue = carry.edgeValue;
|
||||
lane.edgeValueValid = carry.edgeValueValid;
|
||||
@@ -348,9 +399,11 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 清掉“不属于本网格信号”的逐信号条目(合并网格时它们会被整块携带过来)。
|
||||
// 统一重命名,保证编号连续。
|
||||
for (std::size_t index = 0; index < lanes.size(); ++index)
|
||||
{
|
||||
PruneLaneSignalItems(lanes[index]);
|
||||
lanes[index].name = L"网格 " + std::to_wstring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,61 +419,73 @@ namespace jdi
|
||||
|
||||
std::vector<Lane> pieces;
|
||||
pieces.reserve(signals.size());
|
||||
for (const SignalRef& ref : signals)
|
||||
for (std::size_t signalSlot = 0; signalSlot < signals.size(); ++signalSlot)
|
||||
{
|
||||
const SignalRef& ref = signals[signalSlot];
|
||||
// ⚠️ 只有**首个信号**所在的网格继承原网格的实验性功能(用户 2026-10-21 要求:
|
||||
// 拆分后只有“拆分前的网格内的第一个信号”保留已应用的功能)。
|
||||
// 否则拆分会把一个网格的功能(以及它的逐信号条目)复制到每一份上。
|
||||
const bool primary = (signalSlot == 0);
|
||||
Lane lane;
|
||||
lane.autoRange = source.autoRange;
|
||||
lane.yMin = source.yMin;
|
||||
lane.yMax = source.yMax;
|
||||
lane.fitYToSelection = source.fitYToSelection;
|
||||
lane.edgeCount = source.edgeCount;
|
||||
lane.edgeValue = source.edgeValue;
|
||||
lane.edgeValueValid = source.edgeValueValid;
|
||||
lane.edgePanel.open = source.edgePanel.open;
|
||||
lane.edgePanel.collapsed = source.edgePanel.collapsed;
|
||||
lane.edgePanel.x = source.edgePanel.x;
|
||||
lane.edgePanel.y = source.edgePanel.y;
|
||||
lane.edgeCenter = source.edgeCenter;
|
||||
lane.edgeCenterPanel.open = source.edgeCenterPanel.open;
|
||||
lane.edgeCenterPanel.collapsed = source.edgeCenterPanel.collapsed;
|
||||
lane.edgeCenterPanel.x = source.edgeCenterPanel.x;
|
||||
lane.edgeCenterPanel.y = source.edgeCenterPanel.y;
|
||||
lane.weakPeak = source.weakPeak;
|
||||
lane.weakPeakPanel.open = source.weakPeakPanel.open;
|
||||
lane.weakPeakPanel.collapsed = source.weakPeakPanel.collapsed;
|
||||
lane.weakPeakPanel.x = source.weakPeakPanel.x;
|
||||
lane.weakPeakPanel.y = source.weakPeakPanel.y;
|
||||
lane.meanFilter = source.meanFilter;
|
||||
lane.meanFilterPanel.open = source.meanFilterPanel.open;
|
||||
lane.meanFilterPanel.collapsed = source.meanFilterPanel.collapsed;
|
||||
lane.meanFilterPanel.x = source.meanFilterPanel.x;
|
||||
lane.meanFilterPanel.y = source.meanFilterPanel.y;
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰调整条目。
|
||||
lane.meanFilterItems.clear();
|
||||
lane.edgeCenterItems.clear();
|
||||
lane.weakPeakItems.clear();
|
||||
for (const Lane::MeanFilterItem& item : source.meanFilterItems)
|
||||
if (primary)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
lane.edgeCount = source.edgeCount;
|
||||
lane.edgeValue = source.edgeValue;
|
||||
lane.edgeValueValid = source.edgeValueValid;
|
||||
lane.edgePanel = source.edgePanel;
|
||||
lane.edgeCenter = source.edgeCenter;
|
||||
lane.edgeCenterPanel = source.edgeCenterPanel;
|
||||
lane.weakPeak = source.weakPeak;
|
||||
lane.weakPeakPanel = source.weakPeakPanel;
|
||||
lane.meanFilter = source.meanFilter;
|
||||
lane.meanFilterPanel = source.meanFilterPanel;
|
||||
lane.freeMarker = source.freeMarker;
|
||||
lane.freeMarkerCursor = source.freeMarkerCursor;
|
||||
lane.freeMarkerPanel = source.freeMarkerPanel;
|
||||
lane.freeMarkerOpacity = source.freeMarkerOpacity;
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰调整 / 自由标记条目。
|
||||
for (const Lane::MeanFilterItem& item : source.meanFilterItems)
|
||||
{
|
||||
lane.meanFilterItems.push_back(item);
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.meanFilterItems.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const Lane::EdgeCenterItem& item : source.edgeCenterItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
for (const Lane::EdgeCenterItem& item : source.edgeCenterItems)
|
||||
{
|
||||
lane.edgeCenterItems.push_back(item);
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.edgeCenterItems.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const Lane::WeakPeakItem& item : source.weakPeakItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
for (const Lane::WeakPeakItem& item : source.weakPeakItems)
|
||||
{
|
||||
lane.weakPeakItems.push_back(item);
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.weakPeakItems.push_back(item);
|
||||
}
|
||||
}
|
||||
for (const Lane::FreeMarkerItem& item : source.freeMarkerItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.freeMarkerItems.push_back(item);
|
||||
}
|
||||
}
|
||||
for (const SignalRef& collapsed : source.freeMarkerCollapsedGroups)
|
||||
{
|
||||
if (collapsed == ref)
|
||||
{
|
||||
lane.freeMarkerCollapsedGroups.push_back(collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
lane.signals.push_back(ref);
|
||||
PruneLaneSignalItems(lane);
|
||||
pieces.push_back(std::move(lane));
|
||||
}
|
||||
// 用 pieces 替换原网格(插入到原位置,因此其后方的网格整体顺次后移)。
|
||||
|
||||
Reference in new issue
Block a user