实验性功能新增「均值滤波」+ 构建为新信号
- 「实验性功能」二级菜单新增带勾选的「均值滤波」:开启后为本网格每条信号叠加绘制
一条 N 点滑动平均曲线,算法与「边沿计数(中心轴)」的中心平均值曲线**完全一致**
(同一实现 ComputeMovingAverage),配色同样由信号色派生为高对比度颜色、透明度一致。
- 新增均值滤波浮动面板:标题栏可拖拽、✕ 关闭,面板内列出被处理的信号名、
「均值点数 N」内联编辑行,以及底部的「构建为新信号」按钮(悬停高亮 + 手型光标)。
- 「构建为新信号」:把滤波后的曲线作为新信号插入,命名为「<原信号名称> (N点均值)」,
归属到原信号所属组的原信号位置**下方**,成为独立的新信号;其所在网格也移动到
原信号网格的下方。合并网格时一次按钮为该网格内每条可见信号各构建一条。
为此新增 DataSet::InsertDerivedSignal / TabConfig::RemapAfterSignalInsert /
TabConfig::MoveLaneOfSignalBelow / TabConfig::RenameLanes(插入位置之后的信号引用
与可见性列同步重映射)。
- 光标交互与「边沿计数(中心轴)」同构:光标处标注均值滤波曲线的取值
与双光标增量,文案为「值(均值滤波)」/「值(+增量 均值滤波)」;
两条派生曲线的取值胶囊已抽成同一段绘制逻辑。
- 浮动面板种类由 bool centerAxis 改为 EdgePanelKind{COUNT,CENTER,MEAN},
布局/宽度/命中测试/绘制统一走同一套几何。
- 以 VERY_BIG_DATA.CSV 验证:曲线预览、N 编辑、双光标取值标注、
单信号与合并网格两种情况下的新信号命名与插入位置均正确。
This commit is contained in:
1 parent
8ae8d7af4f
commit
f5028b1e18
6 files changed
+885
-210
No files matched your search
@@ -55,9 +55,19 @@ namespace jdi
|
||||
const float kEdgePanelEditHeight = 30.0f; /**< 中心轴面板底部编辑行高度。 */
|
||||
const float kEdgePanelEditFieldWidth = 64.0f; /**< 编辑行数值框的最小宽度。 */
|
||||
const float kEdgePanelEditFieldGap = 8.0f; /**< 编辑行说明文字与数值框之间的间隔。 */
|
||||
const float kEdgePanelButtonHeight = 30.0f; /**< 均值滤波面板底部按钮行高度。 */
|
||||
const wchar_t* const kMeanFilterBuildLabel = L"构建为新信号"; /**< 均值滤波面板底部按钮文案。 */
|
||||
const std::uint64_t kHintHoldMs = 1300; /**< 提示浮层完全显示的停留时长(毫秒)。 */
|
||||
const std::uint64_t kHintFadeMs = 500; /**< 提示浮层的淡出时长(毫秒)。 */
|
||||
|
||||
/**
|
||||
* @brief 实验性功能浮动面板的遍历顺序(命中测试与绘制共用,保证层序一致)。
|
||||
*
|
||||
* 顺序即绘制层序:先绘制的在下方,最后绘制的在最上方。
|
||||
*/
|
||||
const EdgePanelKind kEdgePanelKinds[] = {
|
||||
EdgePanelKind::COUNT, EdgePanelKind::CENTER, EdgePanelKind::MEAN};
|
||||
|
||||
/**
|
||||
* @brief 时间间隔的显示单位。
|
||||
*/
|
||||
@@ -949,6 +959,11 @@ namespace jdi
|
||||
m_edgeCountCache.clear();
|
||||
m_edgeCenterCountCache.clear();
|
||||
m_centerCurveCache.clear();
|
||||
// 信号下标可能已经整体位移:清掉按引用缓存的悬停/拖拽目标,避免指向错误的信号。
|
||||
m_hoverSignal = SignalRef();
|
||||
m_hoverLegend = SignalRef();
|
||||
m_dragSignal = SignalRef();
|
||||
m_dragTargetLane = -1;
|
||||
InvalidateSceneCache();
|
||||
}
|
||||
|
||||
@@ -1458,6 +1473,62 @@ namespace jdi
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool PlotView::LaneMeanFilter(int laneIndex) const
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].meanFilter;
|
||||
}
|
||||
|
||||
bool PlotView::SetLaneMeanFilter(int laneIndex, bool enabled)
|
||||
{
|
||||
if (m_tab == nullptr || laneIndex < 0 || laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
if (lane.meanFilter == enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lane.meanFilter = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
if (lane.meanFilterRadius < 0)
|
||||
{
|
||||
lane.meanFilterRadius = 0;
|
||||
}
|
||||
lane.meanFilterPanelOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
lane.meanFilterPanelOpen = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::ToggleLaneMeanFilter(int laneIndex)
|
||||
{
|
||||
const bool enabled = !LaneMeanFilter(laneIndex);
|
||||
SetLaneMeanFilter(laneIndex, enabled);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool PlotView::TakePendingMeanFilterBuild(int& laneIndex, int& radius)
|
||||
{
|
||||
if (m_pendingMeanFilterLane < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
laneIndex = m_pendingMeanFilterLane;
|
||||
radius = m_pendingMeanFilterRadius;
|
||||
m_pendingMeanFilterLane = -1;
|
||||
m_pendingMeanFilterRadius = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<float>& PlotView::CenterCurveFor(const SignalData& signal, int smooth) const
|
||||
{
|
||||
const int radius = std::max(0, smooth);
|
||||
@@ -1472,74 +1543,9 @@ namespace jdi
|
||||
CenterCurveEntry entry;
|
||||
entry.signal = &signal;
|
||||
entry.smooth = radius;
|
||||
entry.values.assign(signal.values.size(), std::numeric_limits<float>::quiet_NaN());
|
||||
|
||||
const std::size_t count = signal.values.size();
|
||||
if (count > 0)
|
||||
{
|
||||
// 滑动窗口求和:窗口 = [max(0,i-N), min(count-1,i+N)],
|
||||
// 每前进一步只增减边界处的两个采样点,整体 O(n),不额外占用前缀和内存。
|
||||
double sum = 0.0;
|
||||
std::size_t finiteCount = 0;
|
||||
std::size_t windowLow = 0;
|
||||
std::size_t windowHigh = 0; // 闭区间右端(尚未计入时为 0)。
|
||||
|
||||
const auto addValue = [&sum, &finiteCount](double value)
|
||||
{
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
sum += value;
|
||||
++finiteCount;
|
||||
}
|
||||
};
|
||||
const auto removeValue = [&sum, &finiteCount](double value)
|
||||
{
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
sum -= value;
|
||||
--finiteCount;
|
||||
}
|
||||
};
|
||||
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const std::size_t low = (index > static_cast<std::size_t>(radius))
|
||||
? (index - static_cast<std::size_t>(radius))
|
||||
: 0;
|
||||
const std::size_t high = std::min(count - 1, index + static_cast<std::size_t>(radius));
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
windowLow = low;
|
||||
windowHigh = high;
|
||||
for (std::size_t i = windowLow; i <= windowHigh; ++i)
|
||||
{
|
||||
addValue(signal.values[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 左端前移:移出旧左端(若已不在窗口内)。
|
||||
while (windowLow < low)
|
||||
{
|
||||
removeValue(signal.values[windowLow]);
|
||||
++windowLow;
|
||||
}
|
||||
// 右端前移:加入新右端。
|
||||
while (windowHigh < high)
|
||||
{
|
||||
++windowHigh;
|
||||
addValue(signal.values[windowHigh]);
|
||||
}
|
||||
}
|
||||
|
||||
// 中心点本身无效(NaN)时该处不绘制曲线。
|
||||
if (finiteCount > 0 && std::isfinite(signal.values[index]))
|
||||
{
|
||||
entry.values[index] = static_cast<float>(sum / static_cast<double>(finiteCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 中心平均值曲线与「均值滤波」共用同一实现(DataSet 内的自由函数),
|
||||
// 因此预览曲线与"构建为新信号"得到的采样值逐位一致。
|
||||
ComputeMovingAverage(signal.values, radius, entry.values);
|
||||
|
||||
m_centerCurveCache.push_back(std::move(entry));
|
||||
return m_centerCurveCache.back().values;
|
||||
@@ -1567,6 +1573,13 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlotView::MeanFilterValueAt(const DataFile& file, const SignalData& signal, int radius,
|
||||
double time, double& value) const
|
||||
{
|
||||
// 均值滤波曲线与中心平均值曲线是同一算法,取值路径完全一致。
|
||||
return CenterValueAt(file, signal, radius, time, value);
|
||||
}
|
||||
|
||||
void PlotView::EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int smooth,
|
||||
bool hasRange, double rangeMin, double rangeMax,
|
||||
std::size_t& rising, std::size_t& falling) const
|
||||
@@ -1760,7 +1773,7 @@ namespace jdi
|
||||
m_edgeCountCache.push_back(entry);
|
||||
}
|
||||
|
||||
float PlotView::EdgePanelWidth(int laneIndex, bool centerAxis) const
|
||||
float PlotView::EdgePanelWidth(int laneIndex, EdgePanelKind kind) const
|
||||
{
|
||||
if (m_tab == nullptr || m_fonts == nullptr || laneIndex < 0 ||
|
||||
laneIndex >= static_cast<int>(m_tab->Lanes().size()))
|
||||
@@ -1768,9 +1781,13 @@ namespace jdi
|
||||
return kEdgePanelWidth * m_scale;
|
||||
}
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” + 光标区间尾缀)。
|
||||
const std::wstring title = (centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix();
|
||||
// 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” + 光标区间尾缀)。
|
||||
const std::wstring title =
|
||||
meanFilter ? std::wstring(L"均值滤波")
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix());
|
||||
float width = kEdgePanelPad * 2.0f * m_scale + kLaneBadgeWidth * m_scale +
|
||||
8.0f * m_scale + UiMeasureTextWidth(m_fonts->uiSmall, title) +
|
||||
kEdgePanelTitleHeight * m_scale;
|
||||
@@ -1785,6 +1802,11 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
const float nameWidth = UiMeasureTextWidth(m_fonts->uiSmall, resolved.data->Title());
|
||||
if (meanFilter)
|
||||
{
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + nameWidth);
|
||||
continue;
|
||||
}
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
if (centerAxis)
|
||||
@@ -1804,17 +1826,26 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + std::max(nameWidth, countWidth));
|
||||
}
|
||||
|
||||
if (centerAxis)
|
||||
if (centerAxis || meanFilter)
|
||||
{
|
||||
// 底部编辑行 = “平滑点数”说明文字 + 数值框(与菜单内联编辑同一套排版习惯)。
|
||||
// 底部编辑行 = “平滑点数 / 均值点数”说明文字 + 数值框(与菜单内联编辑同一套排版习惯)。
|
||||
const int shown = meanFilter ? lane.meanFilterRadius : lane.edgeCenterSmooth;
|
||||
const std::wstring label = meanFilter ? L"均值点数 N" : L"平滑点数 N";
|
||||
const float editWidth =
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, L"平滑点数 N") + kEdgePanelEditFieldGap * m_scale +
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label) + kEdgePanelEditFieldGap * m_scale +
|
||||
std::max(kEdgePanelEditFieldWidth * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, std::to_wstring(lane.edgeCenterSmooth)) +
|
||||
20.0f * m_scale);
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, std::to_wstring(shown)) + 20.0f * m_scale);
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + editWidth);
|
||||
}
|
||||
|
||||
if (meanFilter)
|
||||
{
|
||||
// 底部按钮行 = 「构建为新信号」,按按钮文字实测宽度确定下限。
|
||||
const float buttonWidth =
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, kMeanFilterBuildLabel) + 28.0f * m_scale;
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + buttonWidth);
|
||||
}
|
||||
|
||||
// 夹在绘图区宽度之内(过窄的网格也不至于把面板挤到外面)。
|
||||
return std::min(width, std::max(120.0f * m_scale, m_plotRect.Width - 8.0f * m_scale));
|
||||
}
|
||||
@@ -2383,9 +2414,10 @@ namespace jdi
|
||||
return gd::RectF(m_editFieldRect.X, m_editFieldRect.Y, nameWidth, height);
|
||||
}
|
||||
|
||||
if (m_editTarget == PlotEditTarget::EDGE_CENTER_SMOOTH)
|
||||
if (m_editTarget == PlotEditTarget::EDGE_CENTER_SMOOTH ||
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_RADIUS)
|
||||
{
|
||||
// 「平滑点数 N」:输入框落在中心轴面板底部的编辑行内(靠右,与显示态对齐)。
|
||||
// 「平滑点数 / 均值点数 N」:输入框落在面板底部的编辑行内(靠右,与显示态对齐)。
|
||||
if (m_editFieldRect.Width <= 0.0f)
|
||||
{
|
||||
return gd::RectF();
|
||||
@@ -2565,6 +2597,18 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
// 「均值点数 N」:只作用于均值滤波曲线的计算,夹在合理范围内(0–1000000)。
|
||||
if (target == PlotEditTarget::MEAN_FILTER_RADIUS)
|
||||
{
|
||||
int radius = static_cast<int>(std::lround(parsed));
|
||||
radius = std::max(0, std::min(radius, 1000000));
|
||||
m_tab->Lanes()[static_cast<std::size_t>(laneIndex)].meanFilterRadius = radius;
|
||||
// 均值半径变化会改变曲线:清掉派生缓存后重绘(面板宽度也随之变化)。
|
||||
m_centerCurveCache.clear();
|
||||
InvalidateSceneCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 直接输入上下限:先固化当前范围,再用新值替换对应端点(保证 max > min)。
|
||||
if (laneIndex >= static_cast<int>(m_lanes.size()))
|
||||
{
|
||||
@@ -2781,12 +2825,11 @@ namespace jdi
|
||||
// 面板**只有标题栏**可以拖拽(显示抓握光标);✕ 与面板主体一律保持原始箭头。
|
||||
if (m_tab != nullptr)
|
||||
{
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
for (const EdgePanelKind kind : kEdgePanelKinds)
|
||||
{
|
||||
const bool centerAxis = (pass == 1);
|
||||
for (int index = 0; index < static_cast<int>(m_lanes.size()); ++index)
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(index, centerAxis);
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(index, kind);
|
||||
if (layout.panel.Width <= 0.0f ||
|
||||
!UiPointInRect(layout.panel, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
@@ -2801,7 +2844,13 @@ namespace jdi
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||
}
|
||||
// 面板主体(含底部编辑行):保持原始箭头。
|
||||
if (kind == EdgePanelKind::MEAN && layout.button.Width > 0.0f &&
|
||||
UiPointInRect(layout.button, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
// 「构建为新信号」按钮:手型光标作为可点击提示。
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 面板主体(含底部编辑行与按钮):保持原始箭头。
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -3029,7 +3078,7 @@ namespace jdi
|
||||
break;
|
||||
}
|
||||
const gd::RectF panel =
|
||||
EdgePanelLayoutFor(m_edgePanelDragLane, m_edgePanelDragCenter).panel;
|
||||
EdgePanelLayoutFor(m_edgePanelDragLane, m_edgePanelDragKind).panel;
|
||||
if (panel.Width <= 0.0f)
|
||||
{
|
||||
break;
|
||||
@@ -3041,15 +3090,21 @@ namespace jdi
|
||||
top = std::max(m_plotRect.Y + 2.0f * m_scale,
|
||||
std::min(top, m_plotRect.Y + m_plotRect.Height - panel.Height - 2.0f * m_scale));
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(m_edgePanelDragLane)];
|
||||
if (m_edgePanelDragCenter)
|
||||
switch (m_edgePanelDragKind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
lane.edgeCenterPanelX = left;
|
||||
lane.edgeCenterPanelY = top;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
lane.meanFilterPanelX = left;
|
||||
lane.meanFilterPanelY = top;
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
lane.edgePanelX = left;
|
||||
lane.edgePanelY = top;
|
||||
break;
|
||||
}
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
@@ -3065,12 +3120,14 @@ namespace jdi
|
||||
// 但该面板所属的网格仍然要高亮(与鼠标直接停在网格上一致)。
|
||||
bool overEdgePanel = false;
|
||||
int panelLane = -1;
|
||||
for (int pass = 0; pass < 2 && !overEdgePanel; ++pass)
|
||||
for (long kindIndex = 0;
|
||||
kindIndex < static_cast<long>(sizeof(kEdgePanelKinds) / sizeof(kEdgePanelKinds[0])) &&
|
||||
!overEdgePanel;
|
||||
++kindIndex)
|
||||
{
|
||||
const bool centerAxis = (pass == 1);
|
||||
for (int index = 0; index < static_cast<int>(m_lanes.size()); ++index)
|
||||
{
|
||||
const gd::RectF panel = EdgePanelLayoutFor(index, centerAxis).panel;
|
||||
const gd::RectF panel = EdgePanelLayoutFor(index, kEdgePanelKinds[kindIndex]).panel;
|
||||
if (panel.Width > 0.0f &&
|
||||
UiPointInRect(panel, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
@@ -3223,16 +3280,17 @@ namespace jdi
|
||||
|
||||
// 实验性功能浮动面板(面板压在波形之上,必须先于信号拖拽判定):
|
||||
// * ✕ → 关闭该网格对应的功能;
|
||||
// * 底部编辑行 → 进入「平滑点数 N」内联编辑;
|
||||
// * 底部编辑行 → 进入「平滑点数 / 均值点数 N」内联编辑;
|
||||
// * 底部按钮 → 触发「构建为新信号」请求;
|
||||
// * 标题栏 → 拖动面板;
|
||||
// * 面板主体 → 消费点击,但**不拖动也不穿透**(用户明确要求只有标题栏可拖拽)。
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
{
|
||||
const bool centerAxis = (pass == 1);
|
||||
const EdgePanelKind kind = kEdgePanelKinds[pass];
|
||||
bool handledPanel = false;
|
||||
for (int index = 0; index < static_cast<int>(m_lanes.size()); ++index)
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(index, centerAxis);
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(index, kind);
|
||||
if (layout.panel.Width <= 0.0f ||
|
||||
!UiPointInRect(layout.panel, fx, fy))
|
||||
{
|
||||
@@ -3243,25 +3301,52 @@ namespace jdi
|
||||
if (UiPointInRect(layout.close, fx, fy))
|
||||
{
|
||||
// ✕ 关闭面板的同时关闭该网格对应的实验性功能。
|
||||
if (centerAxis)
|
||||
switch (kind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
SetLaneEdgeCenterCount(index, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case EdgePanelKind::MEAN:
|
||||
SetLaneMeanFilter(index, false);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
default:
|
||||
SetLaneEdgeCount(index, false);
|
||||
break;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (centerAxis && layout.edit.Width > 0.0f && UiPointInRect(layout.edit, fx, fy) &&
|
||||
m_tab != nullptr)
|
||||
if ((kind == EdgePanelKind::CENTER || kind == EdgePanelKind::MEAN) &&
|
||||
layout.edit.Width > 0.0f && UiPointInRect(layout.edit, fx, fy) && m_tab != nullptr)
|
||||
{
|
||||
// 底部编辑行 → 就地编辑平滑点数 N(输入框落在该行内)。
|
||||
const int smooth = m_tab->Lanes()[static_cast<std::size_t>(index)].edgeCenterSmooth;
|
||||
BeginEdit(PlotEditTarget::EDGE_CENTER_SMOOTH, index, std::to_wstring(smooth),
|
||||
layout.edit);
|
||||
// 底部编辑行 → 就地编辑参数 N(输入框落在该行内)。
|
||||
const Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
if (kind == EdgePanelKind::MEAN)
|
||||
{
|
||||
BeginEdit(PlotEditTarget::MEAN_FILTER_RADIUS, index,
|
||||
std::to_wstring(tabLane.meanFilterRadius), layout.edit);
|
||||
}
|
||||
else
|
||||
{
|
||||
BeginEdit(PlotEditTarget::EDGE_CENTER_SMOOTH, index,
|
||||
std::to_wstring(tabLane.edgeCenterSmooth), layout.edit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (kind == EdgePanelKind::MEAN && layout.button.Width > 0.0f &&
|
||||
UiPointInRect(layout.button, fx, fy) && m_tab != nullptr)
|
||||
{
|
||||
// 「构建为新信号」:只登记请求,真正的信号构建由宿主(Application)完成。
|
||||
SetHint(L"正在构建均值滤波信号(" +
|
||||
std::to_wstring(m_tab->Lanes()[static_cast<std::size_t>(index)].meanFilterRadius) +
|
||||
L" 点均值)…");
|
||||
m_pendingMeanFilterLane = index;
|
||||
m_pendingMeanFilterRadius =
|
||||
m_tab->Lanes()[static_cast<std::size_t>(index)].meanFilterRadius;
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3269,7 +3354,7 @@ namespace jdi
|
||||
{
|
||||
m_dragMode = PlotDragMode::EDGE_PANEL_MOVE;
|
||||
m_edgePanelDragLane = index;
|
||||
m_edgePanelDragCenter = centerAxis;
|
||||
m_edgePanelDragKind = kind;
|
||||
m_edgePanelDragOffset.X = fx - layout.panel.X;
|
||||
m_edgePanelDragOffset.Y = fy - layout.panel.Y;
|
||||
return true;
|
||||
@@ -3363,7 +3448,7 @@ namespace jdi
|
||||
m_cursorGrabOffset = 0;
|
||||
m_edgeDragLane = -1;
|
||||
m_edgePanelDragLane = -1;
|
||||
m_edgePanelDragCenter = false;
|
||||
m_edgePanelDragKind = EdgePanelKind::COUNT;
|
||||
SetHint(std::wstring());
|
||||
m_dragLane = LaneAtY(y);
|
||||
m_dragMode = PlotDragMode::PAN;
|
||||
@@ -3440,7 +3525,7 @@ namespace jdi
|
||||
m_cursorGrabOffset = 0;
|
||||
m_edgeDragLane = -1;
|
||||
m_edgePanelDragLane = -1;
|
||||
m_edgePanelDragCenter = false;
|
||||
m_edgePanelDragKind = EdgePanelKind::COUNT;
|
||||
// 松开左键:粘滞提示结束计时,开始"停留后淡出"。
|
||||
ReleaseHint();
|
||||
(void)x;
|
||||
@@ -4253,6 +4338,9 @@ namespace jdi
|
||||
// 「边沿计数(中心轴)」的开关与平滑点数会改变网格内的曲线绘制 → 参与指纹。
|
||||
mix(Tab().Lanes()[laneIndex].edgeCenter ? 1ull : 0ull);
|
||||
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].edgeCenterSmooth));
|
||||
// 「均值滤波」的开关与均值点数同样会改变网格内的曲线绘制 → 参与指纹。
|
||||
mix(Tab().Lanes()[laneIndex].meanFilter ? 1ull : 0ull);
|
||||
mix(static_cast<std::uint64_t>(Tab().Lanes()[laneIndex].meanFilterRadius));
|
||||
mix(signals.size());
|
||||
for (const SignalRef& ref : signals)
|
||||
{
|
||||
@@ -4610,6 +4698,11 @@ namespace jdi
|
||||
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].edgeCenter
|
||||
? Tab().Lanes()[static_cast<std::size_t>(laneIndex)].edgeCenterSmooth
|
||||
: -1;
|
||||
// 「均值滤波」开启时同样叠加一条 N 点滑动平均曲线(与中心平均值曲线同一算法)。
|
||||
const int meanFilterRadius =
|
||||
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].meanFilter
|
||||
? Tab().Lanes()[static_cast<std::size_t>(laneIndex)].meanFilterRadius
|
||||
: -1;
|
||||
|
||||
for (const SignalRef& ref : laneSignals)
|
||||
{
|
||||
@@ -4729,6 +4822,11 @@ namespace jdi
|
||||
{
|
||||
PaintCenterCurve(g, resolved, lane, centerSmooth);
|
||||
}
|
||||
// 均值滤波曲线(「实验性功能 → 均值滤波」):同样是仅用于预览的叠加曲线。
|
||||
if (meanFilterRadius >= 0)
|
||||
{
|
||||
PaintMeanFilterCurve(g, resolved, lane, meanFilterRadius);
|
||||
}
|
||||
}
|
||||
|
||||
// g.Restore 也会还原绘制状态,此处直接恢复图形状态即可。
|
||||
@@ -5306,68 +5404,87 @@ namespace jdi
|
||||
UiText(g, text, m_fonts->monoSmall, labelRect,
|
||||
dark ? 0xFFFDF6E3u : 0xFF073642u, TextAlign::CENTER);
|
||||
|
||||
// 「边沿计数(中心轴)」开启时,纵向光标还要额外标注**中心平均值曲线**
|
||||
// 「边沿计数(中心轴)」/「均值滤波」开启时,纵向光标还要额外标注**派生曲线**
|
||||
// 在该时刻的取值(它不是信号,因此单独标注,不并入上面的取值文案)。
|
||||
if (m_tab->Lanes()[laneIndex].edgeCenter)
|
||||
// 两条曲线的标注规则完全一致,故共用同一段绘制逻辑。
|
||||
const auto paintDerivedCapsule = [&](int smooth, const std::wstring& suffix,
|
||||
bool meanFilter)
|
||||
{
|
||||
const int smooth = m_tab->Lanes()[laneIndex].edgeCenterSmooth;
|
||||
double centerValue = 0.0;
|
||||
if (CenterValueAt(*resolved.file, *resolved.data, smooth, time, centerValue))
|
||||
double derivedValue = 0.0;
|
||||
const bool ok = meanFilter
|
||||
? MeanFilterValueAt(*resolved.file, *resolved.data, smooth,
|
||||
time, derivedValue)
|
||||
: CenterValueAt(*resolved.file, *resolved.data, smooth, time,
|
||||
derivedValue);
|
||||
if (!ok)
|
||||
{
|
||||
const float rawCenterY = ValueToY(centerValue, lane);
|
||||
const DataMarker centerMarker = MakeDataMarker(
|
||||
rawX, rawCenterY, pinX, clipTop, clipBottom, kCursorHandleReserve * m_scale);
|
||||
return;
|
||||
}
|
||||
|
||||
const float rawCenterY = ValueToY(derivedValue, lane);
|
||||
const DataMarker centerMarker = MakeDataMarker(
|
||||
rawX, rawCenterY, pinX, clipTop, clipBottom, kCursorHandleReserve * m_scale);
|
||||
// 派生数据点的配色与原始信号一致(便于一一对应);
|
||||
// 但**始终按线段模式**绘制(不跟随原信号的散点模式)。
|
||||
const std::uint32_t centerColor = StyleColor(*resolved.style);
|
||||
PaintDataMarker(g, centerMarker, radius, centerColor, m_theme->cardBg);
|
||||
|
||||
// 取值文本:数值在前,尾缀「(中心轴)/(均值滤波)」;双光标的右侧光标
|
||||
// 还要附上增量,形如「(+0.0008 中心轴)」(参考常规信号的增量显示)。
|
||||
std::wstring centerText = FormatValue(derivedValue);
|
||||
centerText += L"(";
|
||||
if (showDelta)
|
||||
{
|
||||
double otherDerived = 0.0;
|
||||
const bool otherOk =
|
||||
meanFilter
|
||||
? MeanFilterValueAt(*resolved.file, *resolved.data, smooth,
|
||||
otherTime, otherDerived)
|
||||
: CenterValueAt(*resolved.file, *resolved.data, smooth, otherTime,
|
||||
otherDerived);
|
||||
if (otherOk)
|
||||
{
|
||||
// 中心轴数据点的配色与原始信号一致(便于一一对应);
|
||||
// 但**始终按线段模式**绘制(不跟随原信号的散点模式)。
|
||||
const std::uint32_t centerColor = StyleColor(*resolved.style);
|
||||
PaintDataMarker(g, centerMarker, radius, centerColor, m_theme->cardBg);
|
||||
|
||||
// 取值文本:数值在前,尾缀「(中心轴)」;双光标的右侧光标还要
|
||||
// 附上增量,形如「(+0.0008 中心轴)」(参考常规信号的增量显示)。
|
||||
std::wstring centerText = FormatValue(centerValue);
|
||||
centerText += L"(";
|
||||
if (showDelta)
|
||||
{
|
||||
double otherCenter = 0.0;
|
||||
if (CenterValueAt(*resolved.file, *resolved.data, smooth, otherTime,
|
||||
otherCenter))
|
||||
{
|
||||
const double delta = centerValue - otherCenter;
|
||||
centerText += (delta < 0.0) ? L"-" : L"+";
|
||||
centerText += FormatValue(std::fabs(delta));
|
||||
centerText += L" ";
|
||||
}
|
||||
}
|
||||
centerText += L"中心轴)";
|
||||
|
||||
const float centerTextWidth =
|
||||
UiTextWidth(g, centerText, m_fonts->monoSmall) + 10.0f * m_scale;
|
||||
float centerLeft = pinX + radius + 4.0f * m_scale;
|
||||
if (centerLeft + centerTextWidth >
|
||||
m_plotRect.X + m_plotRect.Width - 2.0f * m_scale)
|
||||
{
|
||||
centerLeft = pinX - radius - 4.0f * m_scale - centerTextWidth;
|
||||
}
|
||||
centerLeft = std::max(
|
||||
m_plotRect.X + 2.0f * m_scale,
|
||||
std::min(centerLeft, m_plotRect.X + m_plotRect.Width -
|
||||
centerTextWidth - 2.0f * m_scale));
|
||||
float centerTop = centerMarker.y - textHeight * 0.5f;
|
||||
centerTop = std::max(
|
||||
clipTop + 1.0f * m_scale,
|
||||
std::min(centerTop, clipBottom - textHeight - 1.0f * m_scale));
|
||||
gd::RectF centerRect(centerLeft, centerTop, centerTextWidth, textHeight);
|
||||
PlaceCursorLabel(centerRect, centerTop, textHeight, clipTop, clipBottom);
|
||||
// 颜色样式与原始信号一致(同底色/同描边/同文字配色)。
|
||||
const bool centerDark = IsDarkColor(centerColor);
|
||||
UiFillRoundRect(g, centerRect, 3.0f * m_scale,
|
||||
WithAlpha(centerColor, 0xE0));
|
||||
DrawLabelOutline(g, centerRect, 3.0f * m_scale, centerDark);
|
||||
UiText(g, centerText, m_fonts->monoSmall, centerRect,
|
||||
centerDark ? 0xFFFDF6E3u : 0xFF073642u, TextAlign::CENTER);
|
||||
const double delta = derivedValue - otherDerived;
|
||||
centerText += (delta < 0.0) ? L"-" : L"+";
|
||||
centerText += FormatValue(std::fabs(delta));
|
||||
centerText += L" ";
|
||||
}
|
||||
}
|
||||
centerText += suffix;
|
||||
centerText += L")";
|
||||
|
||||
const float centerTextWidth =
|
||||
UiTextWidth(g, centerText, m_fonts->monoSmall) + 10.0f * m_scale;
|
||||
float centerLeft = pinX + radius + 4.0f * m_scale;
|
||||
if (centerLeft + centerTextWidth >
|
||||
m_plotRect.X + m_plotRect.Width - 2.0f * m_scale)
|
||||
{
|
||||
centerLeft = pinX - radius - 4.0f * m_scale - centerTextWidth;
|
||||
}
|
||||
centerLeft = std::max(
|
||||
m_plotRect.X + 2.0f * m_scale,
|
||||
std::min(centerLeft,
|
||||
m_plotRect.X + m_plotRect.Width - centerTextWidth - 2.0f * m_scale));
|
||||
float centerTop = centerMarker.y - textHeight * 0.5f;
|
||||
centerTop = std::max(clipTop + 1.0f * m_scale,
|
||||
std::min(centerTop, clipBottom - textHeight - 1.0f * m_scale));
|
||||
gd::RectF centerRect(centerLeft, centerTop, centerTextWidth, textHeight);
|
||||
PlaceCursorLabel(centerRect, centerTop, textHeight, clipTop, clipBottom);
|
||||
// 颜色样式与原始信号一致(同底色/同描边/同文字配色)。
|
||||
const bool centerDark = IsDarkColor(centerColor);
|
||||
UiFillRoundRect(g, centerRect, 3.0f * m_scale, WithAlpha(centerColor, 0xE0));
|
||||
DrawLabelOutline(g, centerRect, 3.0f * m_scale, centerDark);
|
||||
UiText(g, centerText, m_fonts->monoSmall, centerRect,
|
||||
centerDark ? 0xFFFDF6E3u : 0xFF073642u, TextAlign::CENTER);
|
||||
};
|
||||
|
||||
if (m_tab->Lanes()[laneIndex].edgeCenter)
|
||||
{
|
||||
paintDerivedCapsule(m_tab->Lanes()[laneIndex].edgeCenterSmooth, L"中心轴", false);
|
||||
}
|
||||
if (m_tab->Lanes()[laneIndex].meanFilter)
|
||||
{
|
||||
paintDerivedCapsule(m_tab->Lanes()[laneIndex].meanFilterRadius, L"均值滤波", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5424,7 +5541,7 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
PlotView::EdgePanelLayout PlotView::EdgePanelLayoutFor(int laneIndex, bool centerAxis) const
|
||||
PlotView::EdgePanelLayout PlotView::EdgePanelLayoutFor(int laneIndex, EdgePanelKind kind) const
|
||||
{
|
||||
EdgePanelLayout layout;
|
||||
if (m_tab == nullptr || m_fonts == nullptr || laneIndex < 0 ||
|
||||
@@ -5434,8 +5551,11 @@ namespace jdi
|
||||
return layout;
|
||||
}
|
||||
const Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const bool enabled = centerAxis ? lane.edgeCenter : lane.edgeCount;
|
||||
const bool open = centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen;
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool enabled = meanFilter ? lane.meanFilter : (centerAxis ? lane.edgeCenter : lane.edgeCount);
|
||||
const bool open = meanFilter ? lane.meanFilterPanelOpen
|
||||
: (centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen);
|
||||
if (!enabled || !open)
|
||||
{
|
||||
return layout;
|
||||
@@ -5456,18 +5576,23 @@ namespace jdi
|
||||
}
|
||||
layout.rows = rowCount;
|
||||
|
||||
const float width = EdgePanelWidth(laneIndex, centerAxis);
|
||||
const float width = EdgePanelWidth(laneIndex, kind);
|
||||
const float titleHeight = kEdgePanelTitleHeight * m_scale;
|
||||
const float rowHeight = kEdgePanelRowHeight * m_scale;
|
||||
const float pad = kEdgePanelPad * m_scale;
|
||||
// 中心轴面板在底部固定多出一行「平滑点数」编辑行。
|
||||
const float editHeight = centerAxis ? (kEdgePanelEditHeight * m_scale) : 0.0f;
|
||||
const float height = titleHeight + pad + static_cast<float>(rowCount) * rowHeight * 2.0f + pad +
|
||||
editHeight;
|
||||
// 中心轴 / 均值滤波面板在底部固定多出一行编辑行;均值滤波再多一个按钮行。
|
||||
const float editHeight =
|
||||
(centerAxis || meanFilter) ? (kEdgePanelEditHeight * m_scale) : 0.0f;
|
||||
const float buttonHeight = meanFilter ? (kEdgePanelButtonHeight * m_scale) : 0.0f;
|
||||
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
||||
const float height = titleHeight + pad + static_cast<float>(rowCount) * rowHeight * rowsPerSignal +
|
||||
pad + editHeight + buttonHeight;
|
||||
|
||||
// 首次使用(位置为负)时自动放到绘图区右侧,并各网格依次错开,避免完全重叠。
|
||||
const double storedX = centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX;
|
||||
const double storedY = centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY;
|
||||
const double storedX = meanFilter ? lane.meanFilterPanelX
|
||||
: (centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX);
|
||||
const double storedY = meanFilter ? lane.meanFilterPanelY
|
||||
: (centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY);
|
||||
float left = (storedX >= 0.0)
|
||||
? static_cast<float>(storedX)
|
||||
: (m_plotRect.X + m_plotRect.Width - width - 16.0f * m_scale);
|
||||
@@ -5487,28 +5612,35 @@ namespace jdi
|
||||
layout.title = gd::RectF(left, top, width, titleHeight);
|
||||
const float closeSize = titleHeight;
|
||||
layout.close = gd::RectF(left + width - closeSize, top, closeSize, closeSize);
|
||||
if (centerAxis)
|
||||
if (centerAxis || meanFilter)
|
||||
{
|
||||
const float editRowHeight = kEdgePanelEditHeight * m_scale;
|
||||
layout.edit = gd::RectF(left + pad, top + height - editRowHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), editRowHeight);
|
||||
const float editTop = top + height - buttonHeight - editRowHeight;
|
||||
layout.edit = gd::RectF(left + pad, editTop, std::max(0.0f, width - pad * 2.0f),
|
||||
editRowHeight);
|
||||
}
|
||||
if (meanFilter)
|
||||
{
|
||||
const float buttonRowHeight = kEdgePanelButtonHeight * m_scale;
|
||||
layout.button = gd::RectF(left + pad, top + height - buttonRowHeight,
|
||||
std::max(0.0f, width - pad * 2.0f), buttonRowHeight);
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
gd::RectF PlotView::EdgePanelRect(int laneIndex) const
|
||||
{
|
||||
return EdgePanelLayoutFor(laneIndex, false).panel;
|
||||
return EdgePanelLayoutFor(laneIndex, EdgePanelKind::COUNT).panel;
|
||||
}
|
||||
|
||||
gd::RectF PlotView::EdgePanelCloseRect(int laneIndex) const
|
||||
{
|
||||
return EdgePanelLayoutFor(laneIndex, false).close;
|
||||
return EdgePanelLayoutFor(laneIndex, EdgePanelKind::COUNT).close;
|
||||
}
|
||||
|
||||
gd::RectF PlotView::EdgeCenterPanelRect(int laneIndex) const
|
||||
{
|
||||
return EdgePanelLayoutFor(laneIndex, true).panel;
|
||||
return EdgePanelLayoutFor(laneIndex, EdgePanelKind::CENTER).panel;
|
||||
}
|
||||
|
||||
bool PlotView::IsOnEdgeCursorLine(int x, int y, int& laneIndex) const
|
||||
@@ -5785,6 +5917,14 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int radius) const
|
||||
{
|
||||
// 均值滤波曲线与「边沿计数(中心轴)」的中心平均值曲线是**同一算法、同一视觉效果**
|
||||
// (同色相高对比度的派生配色 + 同透明度),因此直接复用同一段绘制逻辑。
|
||||
PaintCenterCurve(g, resolved, lane, radius);
|
||||
}
|
||||
|
||||
void PlotView::PaintEdgeCursor(gd::Graphics& g, const std::vector<LaneVisual>& lanes)
|
||||
{
|
||||
if (m_tab == nullptr || m_dataSet == nullptr)
|
||||
@@ -5858,14 +5998,17 @@ namespace jdi
|
||||
}
|
||||
|
||||
// ---- 统计浮动面板(最后绘制,覆盖在波形之上)----
|
||||
// 「边沿计数」与「边沿计数(中心轴)」共用同一套样式,仅标题、统计口径与底部编辑行不同。
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
// 「边沿计数」「边沿计数(中心轴)」「均值滤波」共用同一套样式,
|
||||
// 仅标题、内容行与底部编辑行/按钮不同。
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
{
|
||||
const bool centerAxis = (pass == 1);
|
||||
const EdgePanelKind kind = kEdgePanelKinds[pass];
|
||||
const bool centerAxis = (kind == EdgePanelKind::CENTER);
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const Lane& tabLane = tabLanes[index];
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(static_cast<int>(index), centerAxis);
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(static_cast<int>(index), kind);
|
||||
const gd::RectF& panel = layout.panel;
|
||||
if (panel.Width <= 0.0f || panel.Height <= 0.0f)
|
||||
{
|
||||
@@ -5895,7 +6038,8 @@ namespace jdi
|
||||
UiText(g, L"G" + std::to_wstring(index + 1), m_fonts->monoSmall, badgeRect,
|
||||
m_theme->text, TextAlign::CENTER);
|
||||
const std::wstring title =
|
||||
(centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix();
|
||||
meanFilter ? std::wstring(L"均值滤波")
|
||||
: ((centerAxis ? L"中心轴边沿计数" : L"边沿计数") + EdgeRangeSuffix());
|
||||
UiText(g, title, m_fonts->uiSmall,
|
||||
gd::RectF(badgeRect.X + badgeRect.Width + 8.0f * m_scale, panel.Y,
|
||||
panel.Width - badgeRect.Width - 8.0f * m_scale - titleHeight,
|
||||
@@ -5923,7 +6067,8 @@ namespace jdi
|
||||
closeRect.X + crossPad, closeRect.Y + closeRect.Height - crossPad, crossColor,
|
||||
1.6f * m_scale);
|
||||
|
||||
// 每个信号两行:名称(信号色)+ 上升沿/下降沿数量。
|
||||
// 每个信号两行:名称(信号色)+ 上升沿/下降沿数量;
|
||||
// 均值滤波面板只需列出被处理的信号名(一行)。
|
||||
float rowY = panel.Y + titleHeight + pad;
|
||||
for (const SignalRef& ref : tabLane.signals)
|
||||
{
|
||||
@@ -5938,6 +6083,11 @@ namespace jdi
|
||||
UiText(g, resolved.data->Title(), m_fonts->uiSmall,
|
||||
gd::RectF(panel.X + pad, rowY, textWidth, rowHeight), signalColor,
|
||||
TextAlign::LEFT);
|
||||
if (meanFilter)
|
||||
{
|
||||
rowY += rowHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t rising = 0;
|
||||
std::size_t falling = 0;
|
||||
@@ -5960,32 +6110,33 @@ namespace jdi
|
||||
rowY += rowHeight * 2.0f;
|
||||
}
|
||||
|
||||
// 中心轴面板底部固定一行「平滑点数」编辑设置(始终位于面板最下方)。
|
||||
if (centerAxis)
|
||||
// 中心轴 / 均值滤波面板底部固定一行参数编辑设置(始终位于面板最下方)。
|
||||
if (centerAxis || meanFilter)
|
||||
{
|
||||
const gd::RectF& editRect = layout.edit;
|
||||
if (editRect.Width > 0.0f)
|
||||
{
|
||||
const float fieldGap = kEdgePanelEditFieldGap * m_scale;
|
||||
const std::wstring label = L"平滑点数 N";
|
||||
const int shownValue = meanFilter ? tabLane.meanFilterRadius
|
||||
: tabLane.edgeCenterSmooth;
|
||||
const std::wstring label = meanFilter ? L"均值点数 N" : L"平滑点数 N";
|
||||
const float labelWidth = UiMeasureTextWidth(m_fonts->uiSmall, label);
|
||||
UiText(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(editRect.X, editRect.Y, labelWidth, editRect.Height),
|
||||
m_theme->text, TextAlign::LEFT);
|
||||
|
||||
const float fieldWidth = std::max(
|
||||
kEdgePanelEditFieldWidth * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->monoSmall,
|
||||
std::to_wstring(tabLane.edgeCenterSmooth)) +
|
||||
20.0f * m_scale);
|
||||
const float fieldWidth =
|
||||
std::max(kEdgePanelEditFieldWidth * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->monoSmall, std::to_wstring(shownValue)) +
|
||||
20.0f * m_scale);
|
||||
const float fieldLeft = editRect.X + editRect.Width - fieldWidth;
|
||||
const float fieldHeight = editRect.Height - 6.0f * m_scale;
|
||||
const gd::RectF fieldRect(fieldLeft, editRect.Y + 3.0f * m_scale, fieldWidth,
|
||||
fieldHeight);
|
||||
|
||||
const PlotEditTarget editTarget = meanFilter ? PlotEditTarget::MEAN_FILTER_RADIUS
|
||||
: PlotEditTarget::EDGE_CENTER_SMOOTH;
|
||||
const bool editingThis =
|
||||
(m_editTarget == PlotEditTarget::EDGE_CENTER_SMOOTH &&
|
||||
m_editLane == static_cast<int>(index));
|
||||
(m_editTarget == editTarget && m_editLane == static_cast<int>(index));
|
||||
if (editingThis)
|
||||
{
|
||||
UiDrawEditField(g, fieldRect, m_edit, m_fonts->monoSmall, m_scale);
|
||||
@@ -5994,11 +6145,29 @@ namespace jdi
|
||||
{
|
||||
UiFillRoundRect(g, fieldRect, 3.0f * m_scale, m_theme->pressed);
|
||||
UiDrawRoundRect(g, fieldRect, 3.0f * m_scale, m_theme->border, 1.0f);
|
||||
UiText(g, std::to_wstring(tabLane.edgeCenterSmooth), m_fonts->monoSmall,
|
||||
fieldRect, m_theme->textStrong, TextAlign::CENTER);
|
||||
UiText(g, std::to_wstring(shownValue), m_fonts->monoSmall, fieldRect,
|
||||
m_theme->textStrong, TextAlign::CENTER);
|
||||
}
|
||||
(void)labelWidth;
|
||||
(void)fieldGap;
|
||||
}
|
||||
}
|
||||
|
||||
// 均值滤波面板底部按钮行:「构建为新信号」(悬停高亮)。
|
||||
if (meanFilter)
|
||||
{
|
||||
const gd::RectF& buttonRect = layout.button;
|
||||
if (buttonRect.Width > 0.0f)
|
||||
{
|
||||
const gd::RectF inner(buttonRect.X, buttonRect.Y + 2.0f * m_scale,
|
||||
buttonRect.Width, buttonRect.Height - 4.0f * m_scale);
|
||||
const bool hot =
|
||||
(m_dragMode != PlotDragMode::EDGE_PANEL_MOVE) &&
|
||||
UiPointInRect(inner, static_cast<float>(m_mouseX),
|
||||
static_cast<float>(m_mouseY));
|
||||
UiFillRoundRect(g, inner, 4.0f * m_scale,
|
||||
hot ? WithAlpha(m_theme->accent, 0x3A) : m_theme->cardBg);
|
||||
UiDrawRoundRect(g, inner, 4.0f * m_scale, m_theme->accent, 1.2f);
|
||||
UiText(g, kMeanFilterBuildLabel, m_fonts->uiSmall, inner, m_theme->accent,
|
||||
TextAlign::CENTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user