面板限高与滚动改造 + 边沿统计不再卡在“统计中…” + 组名折起真正收起
一、浮动面板(用户报告:下方空白、滚动时画到面板外、未改造的面板让一切交互卡顿) 1. ⭐ 根因:旧「逐条目」排布没有高度上限 —— 288 条信号时面板 13010px(绘图区 846px) ⇒ PanelRectInsideLayer 恒假 ⇒ 面板像素缓存一次都存不进去 ⇒ 每帧整块重画。 现在 5 个面板全部限高 + 可滚动,旧的 itemized 布局/绘制/交互整体删除。 2. 无按钮面板(中心轴边沿计数)不再预留「按钮行 + pad」⇒ 底部空白 55px → 8.8px(1 个 pad)。 3. ⚠️ 统计文字改用 UiTextClipped:GDI 文本不受 GDI+ SetClip 约束,滚动时画到标题栏之外。 4. 「弱峰调整」改造为与均值滤波同款的分组列表(条目栏 + 右侧复选框 + 展开后的两行滑块 + 复位按钮 + 底部「构建为新信号」);新增 Lane::weakPeakScroll / SetLaneWeakPeakScroll, WeakPeakItem 增加 collapsed。 5. 「边沿计数」保留两行排版,但内容区同样限高、可滚动(新增 Lane::edgeCountScroll)。 6. 鼠标指针:列表式面板的条目栏**只有已勾选**才显示手型(未勾选点击无效果,保持箭头); 参数行控件只在参数行展开时给手型 / 左右箭头。 7. 内部标志 m_meanDragCenter / m_editMeanCenter(bool)改为 m_meanDragKind / m_editMeanKind (EdgePanelKind),以便 MEAN / CENTER / PEAK / COUNT 共用同一套滚动与滑块拖拽。 二、边沿统计“全部统计中…且永不更新”(288 信号合并到同一网格) 1. ⭐ 根因:累积状态上限固定 64 < 信号数 288 ⇒ 新建状态总淘汰未完成的 ⇒ 没有任何状态能扫完。 新增 EdgeScanStateLimit() = max(64, 全部信号数 × 2 + 16)。 2. 去掉每状态 60000 的固定配额下限(状态一多就把整帧预算耗光),改为 budget / 未完成状态数; 分母只数未完成状态,短信号扫完后立刻把预算让给长信号。 三、组名折起后“三角已收起但信号行仍在、仍可点选” 1. ⭐ 根因:RequestEdgeScanFrame() 在每个未完成状态里各 Post 一次(单帧最多 288 次), 把优先级最低的 WM_TIMER 饿死 ⇒ AdvanceGroupAnimations 永不执行 ⇒ 折起动画永不结束。 修法:每帧最多 Post 一次(m_edgeScanRequestFrame)。 2. 另一道保险:SignalPanel::EnsureRows() 开头推进一次动画 —— 动画进度只依赖挂钟时间, 不依赖消息能否按时到达。 四、验证(离屏探针 .preflight/p20,全部 SW_HIDE + 合成消息,不占用鼠标/键盘/屏幕) p20.exe 9/9:面板限高且像素缓存真的写进去了、288 个统计全部 complete、中心轴面板底部留白、 组名折起后行集合无该组信号、两组都折起后 signalRows == 0(真实消息 + WM_TIMER)。 p20c.exe 11/11:弱峰面板限高/可滚动/缓存命中、展开与收起、滚动生效、条目栏手型规则、 中心轴滚动时标题栏 0 像素变化(A/B 关掉裁剪 ⇒ 805px)、288 信号空闲帧 2.6ms。 make release / make debug 零 warning。
This commit is contained in:
1 parent
334015c1a9
commit
7a6fcd68e2
9 files changed
+914
-795
No files matched your search
@@ -651,7 +651,8 @@ namespace jdi
|
||||
target == PlotEditTarget::MEAN_ITERATIONS_MAX)
|
||||
{
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const Lane::MeanFilterItem* existing = m_editMeanCenter
|
||||
const bool centerEdit = (m_editMeanKind == EdgePanelKind::CENTER);
|
||||
const Lane::MeanFilterItem* existing = centerEdit
|
||||
? lane.FindEdgeCenterItem(m_editMeanRef)
|
||||
: lane.FindMeanFilterItem(m_editMeanRef);
|
||||
if (existing == nullptr)
|
||||
@@ -662,12 +663,12 @@ namespace jdi
|
||||
{
|
||||
const bool isPoints = (target == PlotEditTarget::MEAN_POINTS_MAX);
|
||||
const int minimum =
|
||||
isPoints ? (m_editMeanCenter ? 0 : kMeanFilterPointsMin) : kMeanFilterIterationsMin;
|
||||
isPoints ? (centerEdit ? 0 : kMeanFilterPointsMin) : kMeanFilterIterationsMin;
|
||||
const int limit = isPoints ? kMeanFilterPointsMaxLimit : kMeanFilterIterationsMaxLimit;
|
||||
const long long value = static_cast<long long>(std::llround(parsed));
|
||||
if (value >= minimum && value <= limit)
|
||||
{
|
||||
Lane::MeanFilterItem& item = m_editMeanCenter
|
||||
Lane::MeanFilterItem& item = centerEdit
|
||||
? lane.EnsureEdgeCenterItem(m_editMeanRef)
|
||||
: lane.EnsureMeanFilterItem(m_editMeanRef);
|
||||
if (isPoints)
|
||||
@@ -682,7 +683,7 @@ namespace jdi
|
||||
std::max(minimum, std::min(item.maxIterations, item.iterations));
|
||||
}
|
||||
m_meanCurveCache.clear();
|
||||
if (m_editMeanCenter)
|
||||
if (centerEdit)
|
||||
{
|
||||
InvalidateEdgeScansFor(ResolveSignal(m_editMeanRef).data, true);
|
||||
}
|
||||
@@ -1070,59 +1071,93 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
}
|
||||
// 「均值滤波 / 中心轴边沿计数」可滚动列表:条目栏(含复选框)→ 手型;
|
||||
// 滑块轨道 → 左右箭头;数值(可点击改上限)与复位按钮 → 手型;
|
||||
// 内容区滚动条 → 上下箭头。与「弱峰调整」同一套观感。
|
||||
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MEAN ||
|
||||
static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::CENTER)
|
||||
// 「均值滤波 / 中心轴边沿计数 / 弱峰调整」可滚动列表:
|
||||
// * 条目栏:**只有该信号已被勾选**时才给手型(未勾选时点击毫无效果,
|
||||
// 用户 2026-11-02 明确要求此时不要改变鼠标指针形状);
|
||||
// * 滑块轨道 → 左右箭头;数值(可点击改上限)与复位按钮 → 手型;
|
||||
// * 内容区滚动条 → 上下箭头。
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowHeaders.size(); ++rowIndex)
|
||||
const EdgePanelKind cursorKind = static_cast<EdgePanelKind>(panelKind);
|
||||
const bool listCursor = (cursorKind == EdgePanelKind::MEAN ||
|
||||
cursorKind == EdgePanelKind::CENTER ||
|
||||
cursorKind == EdgePanelKind::PEAK);
|
||||
const Lane& cursorLane =
|
||||
Tab().Lanes()[static_cast<std::size_t>(panelLane)];
|
||||
if (listCursor)
|
||||
{
|
||||
if (layout.rowHeaders[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowHeaders[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowHeaders.size(); ++rowIndex)
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelValues.size() &&
|
||||
layout.rowLevelValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelValues[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterValues.size() &&
|
||||
layout.rowIterValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterValues[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelResets.size() &&
|
||||
layout.rowLevelResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterResets.size() &&
|
||||
layout.rowIterResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
if (rowIndex < layout.rowIterTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowIterTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
if (layout.rowHeaders[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowHeaders[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
bool checked = false;
|
||||
if (cursorKind == EdgePanelKind::PEAK)
|
||||
{
|
||||
checked = cursorLane.FindWeakPeakItem(
|
||||
layout.rowRefs[rowIndex]) != nullptr;
|
||||
}
|
||||
else if (cursorKind == EdgePanelKind::CENTER)
|
||||
{
|
||||
checked = cursorLane.FindEdgeCenterItem(
|
||||
layout.rowRefs[rowIndex]) != nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
checked = cursorLane.FindMeanFilterItem(
|
||||
layout.rowRefs[rowIndex]) != nullptr;
|
||||
}
|
||||
return checked ? LoadCursorW(nullptr, IDC_HAND) : nullptr;
|
||||
}
|
||||
const bool hasParams =
|
||||
rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f;
|
||||
if (!hasParams)
|
||||
{
|
||||
continue; // 参数行未展开(未勾选 / 已收起)⇒ 该行没有可交互控件
|
||||
}
|
||||
// 「均值点数 / 调整幅度」数值:均值滤波与中心轴可点击改上限(弱峰不能)。
|
||||
if (cursorKind != EdgePanelKind::PEAK &&
|
||||
rowIndex < layout.rowLevelValues.size() &&
|
||||
layout.rowLevelValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelValues[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterValues.size() &&
|
||||
layout.rowIterValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterValues[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelResets.size() &&
|
||||
layout.rowLevelResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterResets.size() &&
|
||||
layout.rowIterResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
if (rowIndex < layout.rowIterTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowIterTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layout.scrollThumb.Width > 0.0f &&
|
||||
@@ -1131,46 +1166,7 @@ namespace jdi
|
||||
return LoadCursorW(nullptr, IDC_SIZENS);
|
||||
}
|
||||
}
|
||||
// 「弱峰调整」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。
|
||||
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::PEAK)
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowIterValues.size(); ++rowIndex)
|
||||
{
|
||||
if (layout.rowIterValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterValues[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 两行滑块右侧的**复位控件**:与其它可点击图标一致,用手型光标提示可点击。
|
||||
if (rowIndex < layout.rowLevelResets.size() &&
|
||||
layout.rowLevelResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterResets.size() &&
|
||||
layout.rowIterResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterResets[rowIndex], static_cast<float>(x),
|
||||
static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
if (rowIndex < layout.rowIterTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowIterTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
if (rowIndex < layout.rowLevelTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale,
|
||||
static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 面板主体(含底部编辑行与参数输入框):保持原始箭头。
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1540,19 +1536,19 @@ namespace jdi
|
||||
break;
|
||||
}
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(m_meanDragLane)];
|
||||
const Lane::MeanFilterItem* existingItem = m_meanDragCenter
|
||||
const Lane::MeanFilterItem* existingItem = (m_meanDragKind == EdgePanelKind::CENTER)
|
||||
? lane.FindEdgeCenterItem(m_meanDragRef)
|
||||
: lane.FindMeanFilterItem(m_meanDragRef);
|
||||
if (existingItem == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Lane::MeanFilterItem& item = m_meanDragCenter
|
||||
Lane::MeanFilterItem& item = (m_meanDragKind == EdgePanelKind::CENTER)
|
||||
? lane.EnsureEdgeCenterItem(m_meanDragRef)
|
||||
: lane.EnsureMeanFilterItem(m_meanDragRef);
|
||||
const bool pointsMode = (m_dragMode == PlotDragMode::MEAN_POINTS);
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(
|
||||
m_meanDragLane, m_meanDragCenter ? EdgePanelKind::CENTER : EdgePanelKind::MEAN);
|
||||
m_meanDragLane, m_meanDragKind);
|
||||
int rowIndex = -1;
|
||||
for (std::size_t candidate = 0; candidate < layout.rowRefs.size(); ++candidate)
|
||||
{
|
||||
@@ -1578,7 +1574,7 @@ namespace jdi
|
||||
if (pointsMode)
|
||||
{
|
||||
// ⚠️ 下限:均值滤波 = 1(用户 2026-10-30 要求不能为 0);中心轴 = 0(0 = 不平滑)。
|
||||
const int floorValue = m_meanDragCenter ? 0 : kMeanFilterPointsMin;
|
||||
const int floorValue = (m_meanDragKind == EdgePanelKind::CENTER) ? 0 : kMeanFilterPointsMin;
|
||||
const int maximum =
|
||||
std::max(floorValue, std::min(kMeanFilterPointsMaxLimit, item.maxPoints));
|
||||
item.points =
|
||||
@@ -1601,7 +1597,7 @@ namespace jdi
|
||||
}
|
||||
m_meanCurveCache.clear();
|
||||
// 中心轴统计依赖参数 ⇒ 参数一变必须定向作废该信号的累积状态。
|
||||
if (m_meanDragCenter)
|
||||
if ((m_meanDragKind == EdgePanelKind::CENTER))
|
||||
{
|
||||
InvalidateEdgeScansFor(ResolveSignal(m_meanDragRef).data, true);
|
||||
}
|
||||
@@ -1750,7 +1746,7 @@ namespace jdi
|
||||
break;
|
||||
}
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(
|
||||
m_markerScrollDragLane, m_meanDragCenter ? EdgePanelKind::CENTER : EdgePanelKind::MEAN);
|
||||
m_markerScrollDragLane, m_meanDragKind);
|
||||
const float travel = layout.scrollTrack.Height - layout.scrollThumb.Height;
|
||||
const float maxScroll =
|
||||
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
|
||||
@@ -1763,10 +1759,23 @@ namespace jdi
|
||||
std::min(static_cast<float>(y) - m_markerScrollGrabOffset,
|
||||
layout.scrollTrack.Y + travel));
|
||||
const float ratio = (thumbTop - layout.scrollTrack.Y) / travel;
|
||||
const bool scrolled =
|
||||
m_meanDragCenter
|
||||
? SetLaneEdgeCenterScroll(m_markerScrollDragLane, ratio * maxScroll)
|
||||
: SetLaneMeanFilterScroll(m_markerScrollDragLane, ratio * maxScroll);
|
||||
const float target = ratio * maxScroll;
|
||||
bool scrolled = false;
|
||||
switch (m_meanDragKind)
|
||||
{
|
||||
case EdgePanelKind::COUNT:
|
||||
scrolled = SetLaneEdgeCountScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::CENTER:
|
||||
scrolled = SetLaneEdgeCenterScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
case EdgePanelKind::PEAK:
|
||||
scrolled = SetLaneWeakPeakScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
default:
|
||||
scrolled = SetLaneMeanFilterScroll(m_markerScrollDragLane, target);
|
||||
break;
|
||||
}
|
||||
if (scrolled)
|
||||
{
|
||||
m_dragMoved = true;
|
||||
@@ -2341,16 +2350,29 @@ namespace jdi
|
||||
// 「中心轴边沿计数」/「均值滤波」/「弱峰调整」面板:逐信号条目
|
||||
// (前两者是「名称 + 复选框 + 两个参数输入框」,弱峰调整是「名称 + 复选框 + 两行滑块」)。
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER ||
|
||||
kind == EdgePanelKind::PEAK) &&
|
||||
kind == EdgePanelKind::PEAK || kind == EdgePanelKind::COUNT) &&
|
||||
m_tab != nullptr)
|
||||
{
|
||||
const bool meanMode = (kind == EdgePanelKind::MEAN);
|
||||
const bool centerMode = (kind == EdgePanelKind::CENTER);
|
||||
const bool boostMode = (kind == EdgePanelKind::PEAK);
|
||||
// 「均值滤波」与「中心轴边沿计数」自 2026-11-02 起共用同一套**可滚动列表**交互
|
||||
//(信号色叠底条目栏 + 右侧复选框 + 展开后的两行滑块)。
|
||||
const bool listMode = (meanMode || centerMode);
|
||||
// 「均值滤波」:组头三角(展开 / 收起参数行)+ 两行参数滑块
|
||||
// 「均值滤波」「中心轴边沿计数」「弱峰调整」自 2026-11-02 起共用同一套
|
||||
//**可滚动列表**交互(信号色叠底条目栏 + 右侧复选框 + 展开后的两行参数滑块)。
|
||||
const bool listMode = (meanMode || centerMode || boostMode);
|
||||
// 「边沿计数」仍是最简单的两行列表,只需要滚动条拖动。
|
||||
if (kind == EdgePanelKind::COUNT)
|
||||
{
|
||||
if (layout.scrollThumb.Width > 0.0f &&
|
||||
UiPointInRect(layout.scrollThumb, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::MEAN_SCROLL;
|
||||
m_markerScrollDragLane = index;
|
||||
m_markerScrollGrabOffset = fy - layout.scrollThumb.Y;
|
||||
m_meanDragKind = EdgePanelKind::COUNT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 「列表式面板」:组头三角(展开 / 收起参数行)+ 两行参数滑块
|
||||
//(轨道拖动 / 点击数值改上限 / 右侧复位按钮)。
|
||||
// ⚠️ 所有命中都要求落在**内容区**之内:滚动到可视范围之外的行仍保留矩形,
|
||||
// 若不加这条限制,它们会抢走底部「构建为新信号」按钮的点击。
|
||||
@@ -2375,11 +2397,110 @@ namespace jdi
|
||||
m_dragMode = PlotDragMode::MEAN_SCROLL;
|
||||
m_markerScrollDragLane = index;
|
||||
m_markerScrollGrabOffset = fy - layout.scrollThumb.Y;
|
||||
m_meanDragCenter = centerMode;
|
||||
m_meanDragKind = kind;
|
||||
return true;
|
||||
}
|
||||
const bool insideContent =
|
||||
UiPointInRect(layout.content, fx, fy);
|
||||
// 「弱峰调整」:两行滑块 = 调整幅度(-400% … +400%)/ 迭代次数(1 … 自定上限)。
|
||||
// 语义与该面板旧式排版完全一致,只是外观改成与均值滤波同款的可滚动列表。
|
||||
if (boostMode)
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
if (!insideContent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const Lane::WeakPeakItem* peakItem = meanLane.FindWeakPeakItem(ref);
|
||||
const bool hasParams =
|
||||
peakItem != nullptr && rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f;
|
||||
if (!hasParams)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Lane::WeakPeakItem& item = meanLane.EnsureWeakPeakItem(ref);
|
||||
// 复位:调整幅度 → 0%
|
||||
if (rowIndex < layout.rowLevelResets.size() &&
|
||||
layout.rowLevelResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelResets[rowIndex], fx, fy))
|
||||
{
|
||||
item.amountPercent = 0;
|
||||
m_weakPeakCurveCache.clear();
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 复位:迭代次数 → 1(上限恢复为默认上限)。
|
||||
if (rowIndex < layout.rowIterResets.size() &&
|
||||
layout.rowIterResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterResets[rowIndex], fx, fy))
|
||||
{
|
||||
item.iterations = kWeakPeakIterationsMin;
|
||||
item.maxIterations = kWeakPeakIterationsDefaultMax;
|
||||
m_weakPeakCurveCache.clear();
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 迭代次数行的**数值文本**:点击后整行变成输入框以修改滑块上限。
|
||||
// ⚠️ 必须放在轨道命中之前(数值区在轨道右侧,命中区不重叠,但顺序更安全)。
|
||||
if (rowIndex < layout.rowIterValues.size() &&
|
||||
layout.rowIterValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterValues[rowIndex], fx, fy))
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
m_peakEditCurrentMax = item.maxIterations;
|
||||
const gd::RectF& track = layout.rowIterTracks[rowIndex];
|
||||
const gd::RectF& value = layout.rowIterValues[rowIndex];
|
||||
const gd::RectF rowRect(track.X, value.Y, value.X + value.Width - track.X,
|
||||
value.Height);
|
||||
BeginEdit(PlotEditTarget::PEAK_ITERATIONS_MAX, index, L"", rowRect);
|
||||
m_edit.Begin(L"", false, EditFilter::BYTE, 0u);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 拖动「迭代次数」滑块。
|
||||
if (rowIndex < layout.rowIterTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowIterTracks[rowIndex], m_scale, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::PEAK_ITERATIONS;
|
||||
m_peakDragLane = index;
|
||||
m_peakDragRef = ref;
|
||||
const double ratio =
|
||||
UiSliderRatioFromX(layout.rowIterTracks[rowIndex], fx);
|
||||
const int peakMax = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit, item.maxIterations));
|
||||
item.iterations = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(peakMax,
|
||||
kWeakPeakIterationsMin +
|
||||
static_cast<int>(std::lround(
|
||||
ratio * (peakMax - kWeakPeakIterationsMin)))));
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
// 拖动「调整幅度」滑块(0 在正中间 ⇒ ±400%)。
|
||||
if (rowIndex < layout.rowLevelTracks.size() &&
|
||||
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::PEAK_LEVEL;
|
||||
m_peakDragLane = index;
|
||||
m_peakDragRef = ref;
|
||||
item.amountPercent = static_cast<int>(std::lround(
|
||||
(UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 2.0 - 1.0) *
|
||||
100.0 * kWeakPeakSliderScale));
|
||||
item.amountPercent = std::max(
|
||||
kWeakPeakAmountMin,
|
||||
std::min(kWeakPeakAmountMax, item.amountPercent));
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
@@ -2433,7 +2554,7 @@ namespace jdi
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
m_meanEditCurrentMax = item.maxPoints;
|
||||
m_editMeanCenter = centerMode;
|
||||
m_editMeanKind = kind;
|
||||
const gd::RectF& track = layout.rowLevelTracks[rowIndex];
|
||||
const gd::RectF& value = layout.rowLevelValues[rowIndex];
|
||||
const gd::RectF rowRect(track.X, value.Y,
|
||||
@@ -2451,7 +2572,7 @@ namespace jdi
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
m_meanEditCurrentMax = item.maxIterations;
|
||||
m_editMeanCenter = centerMode;
|
||||
m_editMeanKind = kind;
|
||||
const gd::RectF& track = layout.rowIterTracks[rowIndex];
|
||||
const gd::RectF& value = layout.rowIterValues[rowIndex];
|
||||
const gd::RectF rowRect(track.X, value.Y,
|
||||
@@ -2472,7 +2593,7 @@ namespace jdi
|
||||
m_dragMode = PlotDragMode::MEAN_POINTS;
|
||||
m_meanDragLane = index;
|
||||
m_meanDragRef = ref;
|
||||
m_meanDragCenter = centerMode;
|
||||
m_meanDragKind = kind;
|
||||
const double ratio = UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx);
|
||||
item.points = std::max(
|
||||
floorValue,
|
||||
@@ -2495,7 +2616,7 @@ namespace jdi
|
||||
m_dragMode = PlotDragMode::MEAN_ITERATIONS;
|
||||
m_meanDragLane = index;
|
||||
m_meanDragRef = ref;
|
||||
m_meanDragCenter = centerMode;
|
||||
m_meanDragKind = kind;
|
||||
const double ratio = UiSliderRatioFromX(layout.rowIterTracks[rowIndex], fx);
|
||||
item.iterations = std::max(
|
||||
kMeanFilterIterationsMin,
|
||||
@@ -2511,198 +2632,84 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 条目栏(信号色叠底那一整行):
|
||||
// * 点在右端复选框上 → 切换"是否启用该信号的预览"(与复选框同一语义,
|
||||
// 因为整行已被本函数消费,复选框自己收不到点击了);
|
||||
// * 已启用 → 整行切换参数行的收起 / 展开;
|
||||
// * **未启用 → 什么都不做**(用户 2026-10-30 明确要求:未勾选前点击条目栏
|
||||
// 既不能自动勾选、也不能展开 / 收起,只能点复选框)。
|
||||
// 仍然消费这次点击(不穿透到底层的平移/选区等)。
|
||||
if (rowIndex < layout.rowHeaders.size() &&
|
||||
layout.rowHeaders[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowHeaders[rowIndex], fx, fy))
|
||||
{
|
||||
const bool onCheckBox =
|
||||
rowIndex < layout.rowChecks.size() &&
|
||||
layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowChecks[rowIndex], fx, fy);
|
||||
Lane::MeanFilterItem* existing =
|
||||
const_cast<Lane::MeanFilterItem*>(findItem(ref));
|
||||
if (onCheckBox)
|
||||
{
|
||||
if (centerMode)
|
||||
{
|
||||
ToggleLaneEdgeCenterSignal(index, ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
}
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
}
|
||||
else if (existing != nullptr)
|
||||
{
|
||||
// 已启用:整行切换参数行的收起 / 展开。
|
||||
existing->collapsed = !existing->collapsed;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Lane& tabLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
|
||||
|
||||
// 「弱峰调整」:先判两行滑块(数值文本 → 改上限;轨道 → 拖动)。
|
||||
if (boostMode)
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
const bool hasLevel = rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f;
|
||||
if (!hasLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (tabLane.FindWeakPeakItem(ref) == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Lane::WeakPeakItem& item = tabLane.EnsureWeakPeakItem(ref);
|
||||
// 两行滑块右侧的**复位控件**:点击恢复该行参数的默认值
|
||||
//(幅度 → 0;迭代次数 → 1 且上限回到初始上限 `kWeakPeakIterationsDefaultMax`)。
|
||||
// 与「系数/偏移」二级面板里的还原图标同一交互语义(面板保持打开、就地回显)。
|
||||
if (rowIndex < layout.rowLevelResets.size() &&
|
||||
layout.rowLevelResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowLevelResets[rowIndex], fx, fy))
|
||||
{
|
||||
item.amountPercent = 0;
|
||||
m_weakPeakCurveCache.clear();
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
if (rowIndex < layout.rowIterResets.size() &&
|
||||
layout.rowIterResets[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterResets[rowIndex], fx, fy))
|
||||
{
|
||||
item.iterations = kWeakPeakIterationsMin;
|
||||
item.maxIterations = kWeakPeakIterationsDefaultMax;
|
||||
m_weakPeakCurveCache.clear();
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 迭代次数行右侧的**数值文本**:点击后整行变成输入框以修改滑块上限。
|
||||
// ⚠️ 必须放在轨道命中之前(数值区在轨道右侧,命中区不重叠,但顺序更安全)。
|
||||
if (layout.rowIterValues[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterValues[rowIndex], fx, fy))
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
m_peakEditCurrentMax = item.maxIterations;
|
||||
// 输入框以**空内容**开始:空内容时显示当前上限作为灰色提示。
|
||||
// 输入框覆盖"整条滑块行"(从轨道左端到数值文本右端)。
|
||||
const gd::RectF& track = layout.rowIterTracks[rowIndex];
|
||||
const gd::RectF& value = layout.rowIterValues[rowIndex];
|
||||
const gd::RectF rowRect(track.X, value.Y, value.X + value.Width - track.X,
|
||||
value.Height);
|
||||
BeginEdit(PlotEditTarget::PEAK_ITERATIONS_MAX, index, L"", rowRect);
|
||||
// `BeginEdit` 默认用 NUMBER 过滤(允许负号/小数点);这里改为**纯数字**,
|
||||
// 从源头保证"只允许输入正整数"(负数、小数根本无法键入,粘贴同样被清洗)。
|
||||
m_edit.Begin(L"", false, EditFilter::BYTE, 0u);
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
if (layout.rowIterTracks[rowIndex].Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.rowIterTracks[rowIndex], m_scale, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::PEAK_ITERATIONS;
|
||||
m_peakDragLane = index;
|
||||
m_peakDragRef = ref;
|
||||
const double ratio = UiSliderRatioFromX(layout.rowIterTracks[rowIndex], fx);
|
||||
const int peakMax = std::max(kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit,
|
||||
item.maxIterations));
|
||||
item.iterations = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(peakMax, kWeakPeakIterationsMin +
|
||||
static_cast<int>(std::lround(
|
||||
ratio * (peakMax - kWeakPeakIterationsMin)))));
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
if (layout.rowLevelTracks[rowIndex].Width > 0.0f &&
|
||||
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale, fx, fy))
|
||||
{
|
||||
m_dragMode = PlotDragMode::PEAK_LEVEL;
|
||||
m_peakDragLane = index;
|
||||
m_peakDragRef = ref;
|
||||
// 单击轨道:直接写入该处对应的幅度(-400% … +400%,0 在正中间)。
|
||||
item.amountPercent = static_cast<int>(std::lround(
|
||||
(UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 2.0 - 1.0) *
|
||||
100.0 * kWeakPeakSliderScale));
|
||||
item.amountPercent = std::max(
|
||||
kWeakPeakAmountMin,
|
||||
std::min(kWeakPeakAmountMax, item.amountPercent));
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 条目栏(信号色叠底那一整行):
|
||||
// * 点在右端复选框上 → 切换「是否启用该信号的预览」;
|
||||
// * 已启用 → 整行切换参数行的收起 / 展开;
|
||||
// * **未启用 → 什么都不做**(用户 2026-10-30 明确要求:未勾选前点击条目栏
|
||||
// 既不能自动勾选、也不能展开 / 收起,只能点复选框;此时鼠标指针也保持箭头)。
|
||||
// 仍然消费这次点击(不穿透到底层的平移 / 选区等)。
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
// 第一排右侧的复选框 → 切换该信号的预览勾选状态。
|
||||
if (layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowChecks[rowIndex], fx, fy))
|
||||
if (!insideContent)
|
||||
{
|
||||
if (meanMode)
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
}
|
||||
else if (boostMode)
|
||||
break;
|
||||
}
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
if (rowIndex >= layout.rowHeaders.size() ||
|
||||
layout.rowHeaders[rowIndex].Width <= 0.0f ||
|
||||
!UiPointInRect(layout.rowHeaders[rowIndex], fx, fy))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool onCheckBox =
|
||||
rowIndex < layout.rowChecks.size() &&
|
||||
layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowChecks[rowIndex], fx, fy);
|
||||
bool checked = false;
|
||||
bool collapsed = false;
|
||||
if (boostMode)
|
||||
{
|
||||
const Lane::WeakPeakItem* peakItem = meanLane.FindWeakPeakItem(ref);
|
||||
checked = (peakItem != nullptr);
|
||||
collapsed = (peakItem != nullptr && peakItem->collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Lane::MeanFilterItem* meanItem = findItem(ref);
|
||||
checked = (meanItem != nullptr);
|
||||
collapsed = (meanItem != nullptr && meanItem->collapsed);
|
||||
}
|
||||
if (onCheckBox)
|
||||
{
|
||||
if (boostMode)
|
||||
{
|
||||
ToggleLaneWeakPeakSignal(index, ref);
|
||||
}
|
||||
else
|
||||
else if (centerMode)
|
||||
{
|
||||
ToggleLaneEdgeCenterSignal(index, ref);
|
||||
}
|
||||
// 勾选变化只改变该信号“是否显示”,它的统计状态(与参数相关)依然有效,
|
||||
// 其它信号更是完全无关 ⇒ **不要**清任何缓存(清掉就会让所有统计重算)。
|
||||
// 这里**不**整体作废场景缓存(交给 `m_sceneDerived` 快照驱动局部重画)。
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
else
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
}
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
}
|
||||
// 第二排的两个输入框 → 就地编辑该信号的参数(仅 MEAN / CENTER 面板;
|
||||
// 弱峰调整用的是滑块,已在上面处理)。
|
||||
if (layout.rowPoints[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowPoints[rowIndex], fx, fy))
|
||||
else if (checked)
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
const Lane::MeanFilterItem* item =
|
||||
meanMode ? tabLane.FindMeanFilterItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref);
|
||||
BeginEdit(meanMode ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS,
|
||||
index, std::to_wstring(item != nullptr ? item->points : 5),
|
||||
layout.rowPoints[rowIndex]);
|
||||
return true;
|
||||
}
|
||||
if (layout.rowIterations[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowIterations[rowIndex], fx, fy))
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
const Lane::MeanFilterItem* item =
|
||||
meanMode ? tabLane.FindMeanFilterItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref);
|
||||
BeginEdit(meanMode ? PlotEditTarget::MEAN_FILTER_ITERATIONS
|
||||
: PlotEditTarget::EDGE_CENTER_ITERATIONS,
|
||||
index, std::to_wstring(item != nullptr ? item->iterations : 1),
|
||||
layout.rowIterations[rowIndex]);
|
||||
return true;
|
||||
// 已启用:整行切换参数行的收起 / 展开。
|
||||
if (boostMode)
|
||||
{
|
||||
Lane::WeakPeakItem* item =
|
||||
const_cast<Lane::WeakPeakItem*>(meanLane.FindWeakPeakItem(ref));
|
||||
item->collapsed = !collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Lane::MeanFilterItem* item =
|
||||
const_cast<Lane::MeanFilterItem*>(findItem(ref));
|
||||
item->collapsed = !collapsed;
|
||||
}
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (boostMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy))
|
||||
{
|
||||
@@ -3208,7 +3215,7 @@ namespace jdi
|
||||
// 「均值滤波 / 中心轴边沿计数」参数滑块拖拽结束:同样清掉目标。
|
||||
m_meanDragLane = -1;
|
||||
m_meanDragRef = SignalRef();
|
||||
m_meanDragCenter = false;
|
||||
m_meanDragKind = EdgePanelKind::MEAN;
|
||||
// 「标记透明度」滑块 / 面板内容滚动条拖拽结束。
|
||||
m_markerOpacityDragLane = -1;
|
||||
m_markerScrollDragLane = -1;
|
||||
@@ -3260,9 +3267,10 @@ namespace jdi
|
||||
panelLane, panelKind))
|
||||
{
|
||||
const EdgePanelKind hoveredKind = static_cast<EdgePanelKind>(panelKind);
|
||||
// 「均值滤波 / 中心轴边沿计数」面板:内容区超高时用滚轮翻页
|
||||
// 「均值滤波 / 中心轴边沿计数 / 弱峰调整 / 边沿计数」面板:内容区超高时用滚轮翻页
|
||||
//(上百个信号合并时必备)。
|
||||
if (hoveredKind == EdgePanelKind::MEAN || hoveredKind == EdgePanelKind::CENTER)
|
||||
if (hoveredKind == EdgePanelKind::MEAN || hoveredKind == EdgePanelKind::CENTER ||
|
||||
hoveredKind == EdgePanelKind::PEAK || hoveredKind == EdgePanelKind::COUNT)
|
||||
{
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, hoveredKind);
|
||||
if (layout.content.Width > 0.0f &&
|
||||
@@ -3277,16 +3285,27 @@ namespace jdi
|
||||
Tab().Lanes()[static_cast<std::size_t>(panelLane)];
|
||||
const float current = (hoveredKind == EdgePanelKind::CENTER)
|
||||
? wheelLane.edgeCenterScroll
|
||||
: (hoveredKind == EdgePanelKind::PEAK)
|
||||
? wheelLane.weakPeakScroll
|
||||
: (hoveredKind == EdgePanelKind::COUNT)
|
||||
? wheelLane.edgeCountScroll
|
||||
: wheelLane.meanFilterScroll;
|
||||
const float next = current - static_cast<float>(notches * 40.0 * m_scale);
|
||||
const float target = std::max(0.0f, std::min(next, maxScroll));
|
||||
if (hoveredKind == EdgePanelKind::CENTER)
|
||||
switch (hoveredKind)
|
||||
{
|
||||
case EdgePanelKind::CENTER:
|
||||
SetLaneEdgeCenterScroll(panelLane, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case EdgePanelKind::PEAK:
|
||||
SetLaneWeakPeakScroll(panelLane, target);
|
||||
break;
|
||||
case EdgePanelKind::COUNT:
|
||||
SetLaneEdgeCountScroll(panelLane, target);
|
||||
break;
|
||||
default:
|
||||
SetLaneMeanFilterScroll(panelLane, target);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4171,5 +4190,4 @@ namespace jdi
|
||||
}
|
||||
|
||||
|
||||
} // namespace jdi
|
||||
|
||||
} // namespace jdi
|
||||
Reference in new issue
Block a user