288 信号卡顿根治 + 中心轴面板重做 + 标签页默认状态

一、卡顿真因(实测 29.8ms/帧 → 6.6ms/帧)
1. 中心轴面板高度没有上限:288 个信号时面板高 9996px(绘图区只有 1141px)
   ⇒ PanelRectInsideLayer 恒为假 ⇒ 面板像素缓存一次都存不进去 ⇒ 每帧全量重画。
2. PanelGlobalKey 混入鼠标驱动状态(m_hoverSignal / m_hoverLane / m_hoverCursor /
   CursorTime / EdgeRangeMin/Max)⇒ 鼠标一动键就变 ⇒ 所有面板每帧重画;
   PanelSlotKey 里还会为每个信号调一次 EdgeCenterCountsFor(单帧 288 次查询)。
3. 缓存命中路径的投影(与面板同大的半透明抗锯齿圆角矩形)每帧重画,GDI+ 实测 > 20ms。

修法:
* 新增 PlotView::InputIsInteractive()(拖拽中 / 鼠标 140ms 内移动过)。交互期间冻结
  全部鼠标驱动项与统计数字、跳过投影、统计预算降到 8 万(静止时 40 万)。
  m_meanRowHoverLane/Ref 例外 —— 它直接改面板像素且只在进出某行时变化。
* kEdgeScanPairsPerFrame 由 1,200,000 降为 400,000。
* 中心轴面板重做为可滚动列表 ⇒ 面板高度被绘图区夹住、缓存可用、只画可见行。

二、「中心轴边沿计数」面板重做(与「均值滤波」同风格)
条目栏为信号色叠底的可折叠栏 + 右端复选框;栏下同一行显示「上升沿 / 下降沿」;
再下面两行滑块(均值点数 / 迭代次数,标签不带 X/N),数值可点击输入上限、
右侧复位按钮;整个面板可滚动;未勾选时条目栏不可展开。
新增 Lane::edgeCenterScroll + SetLaneEdgeCenterScroll;交互复用 MEAN_* 拖拽模式 +
m_meanDragCenter / m_editMeanCenter 标志分派;光标形状与均值滤波一致。

三、标签页
* 唯一标签页点 ✕ 不再弹警告:直接 erase 后 NewTab(true),名字必然为「视图 1」。
* 新建标签页(含手动新建)默认:组全部折起、无选中信号(清空全局 Selected)、
  样式区无目标。

四、验证
.preflight/bugcheck.exe 59/59(A1 空闲 0.7ms、A2 平移 2.5ms、A3 双光标 2.4ms、
A4 自由标记 5.2ms、A5 中心轴拖动 6.6ms;B3 真实 WM_TIMER+WM_PAINT 路径;
B5 快速连点与动画反向;C 中心轴面板交互;D 标签页默认状态)
panelcheck 71/71、markercheck 13/13、ringcheck 6/6
make release / make debug 零 warning
This commit is contained in:
DeepSeek-V4.1-Flash committed 2026-11-02 03:33:28 +08:00
1 parent 8271879ede
commit 944c074ab3
11 files changed
+618 -122

No files matched your search

+209 -49
View File
@@ -651,19 +651,25 @@ namespace jdi
target == PlotEditTarget::MEAN_ITERATIONS_MAX)
{
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
if (lane.FindMeanFilterItem(m_editMeanRef) == nullptr)
const Lane::MeanFilterItem* existing = m_editMeanCenter
? lane.FindEdgeCenterItem(m_editMeanRef)
: lane.FindMeanFilterItem(m_editMeanRef);
if (existing == nullptr)
{
return true;
}
if (parsedOk)
{
const bool isPoints = (target == PlotEditTarget::MEAN_POINTS_MAX);
const int minimum = isPoints ? kMeanFilterPointsMin : kMeanFilterIterationsMin;
const int minimum =
isPoints ? (m_editMeanCenter ? 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 = lane.EnsureMeanFilterItem(m_editMeanRef);
Lane::MeanFilterItem& item = m_editMeanCenter
? lane.EnsureEdgeCenterItem(m_editMeanRef)
: lane.EnsureMeanFilterItem(m_editMeanRef);
if (isPoints)
{
item.maxPoints = static_cast<int>(value);
@@ -676,6 +682,10 @@ namespace jdi
std::max(minimum, std::min(item.maxIterations, item.iterations));
}
m_meanCurveCache.clear();
if (m_editMeanCenter)
{
InvalidateEdgeScansFor(ResolveSignal(m_editMeanRef).data, true);
}
}
}
return true;
@@ -1060,6 +1070,67 @@ namespace jdi
}
}
}
// 「均值滤波 / 中心轴边沿计数」可滚动列表:条目栏(含复选框)→ 手型;
// 滑块轨道 → 左右箭头;数值(可点击改上限)与复位按钮 → 手型;
// 内容区滚动条 → 上下箭头。与「弱峰调整」同一套观感。
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::MEAN ||
static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::CENTER)
{
for (std::size_t rowIndex = 0; rowIndex < layout.rowHeaders.size(); ++rowIndex)
{
if (layout.rowHeaders[rowIndex].Width > 0.0f &&
UiPointInRect(layout.rowHeaders[rowIndex], static_cast<float>(x),
static_cast<float>(y)))
{
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.scrollThumb.Width > 0.0f &&
UiPointInRect(layout.scrollThumb, static_cast<float>(x), static_cast<float>(y)))
{
return LoadCursorW(nullptr, IDC_SIZENS);
}
}
// 「弱峰调整」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。
if (static_cast<EdgePanelKind>(panelKind) == EdgePanelKind::PEAK)
{
@@ -1164,11 +1235,32 @@ namespace jdi
return nullptr;
}
bool PlotView::InputIsInteractive() const
{
if (m_dragMode != PlotDragMode::NONE)
{
return true;
}
if (m_lastInputActivityMs == 0)
{
return false;
}
const std::uint64_t now = GetTickCount64();
return (now >= m_lastInputActivityMs) &&
((now - m_lastInputActivityMs) < kEdgeScanInteractiveIdleMs);
}
bool PlotView::OnMouseMove(int x, int y)
{
const bool mouseMoved = (x != m_mouseX) || (y != m_mouseY);
m_mouseX = x;
m_mouseY = y;
// 交互活动时间戳:`Paint` 用它决定边沿统计本帧用「交互预算」还是「静止预算」。
// 只有真正的移动才更新 —— 悬停不动时统计应当全速推进。
if (mouseMoved)
{
m_lastInputActivityMs = GetTickCount64();
}
if (!HasDrawableContent())
{
@@ -1441,21 +1533,26 @@ namespace jdi
case PlotDragMode::MEAN_POINTS:
case PlotDragMode::MEAN_ITERATIONS:
{
// 「均值滤波」某信号的参数滑块:量程 = 下限 … 该信号自己的上限。
// 「均值滤波 / 中心轴边沿计数」某信号的参数滑块:量程 = 下限 … 该信号自己的上限。
if (m_tab == nullptr || m_meanDragLane < 0 ||
m_meanDragLane >= static_cast<int>(m_tab->Lanes().size()))
{
break;
}
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(m_meanDragLane)];
if (lane.FindMeanFilterItem(m_meanDragRef) == nullptr)
const Lane::MeanFilterItem* existingItem = m_meanDragCenter
? lane.FindEdgeCenterItem(m_meanDragRef)
: lane.FindMeanFilterItem(m_meanDragRef);
if (existingItem == nullptr)
{
break;
}
Lane::MeanFilterItem& item = lane.EnsureMeanFilterItem(m_meanDragRef);
Lane::MeanFilterItem& item = m_meanDragCenter
? lane.EnsureEdgeCenterItem(m_meanDragRef)
: lane.EnsureMeanFilterItem(m_meanDragRef);
const bool pointsMode = (m_dragMode == PlotDragMode::MEAN_POINTS);
const EdgePanelLayout layout =
EdgePanelLayoutFor(m_meanDragLane, EdgePanelKind::MEAN);
const EdgePanelLayout layout = EdgePanelLayoutFor(
m_meanDragLane, m_meanDragCenter ? EdgePanelKind::CENTER : EdgePanelKind::MEAN);
int rowIndex = -1;
for (std::size_t candidate = 0; candidate < layout.rowRefs.size(); ++candidate)
{
@@ -1480,13 +1577,15 @@ namespace jdi
UiSliderRatioFromX(tracks[static_cast<std::size_t>(rowIndex)], fx);
if (pointsMode)
{
const int maximum = std::max(
kMeanFilterPointsMin, std::min(kMeanFilterPointsMaxLimit, item.maxPoints));
item.points = std::max(
kMeanFilterPointsMin,
std::min(maximum,
kMeanFilterPointsMin +
static_cast<int>(std::lround(ratio * (maximum - kMeanFilterPointsMin)))));
// ⚠️ 下限:均值滤波 = 1(用户 2026-10-30 要求不能为 0);中心轴 = 0(0 = 不平滑)。
const int floorValue = m_meanDragCenter ? 0 : kMeanFilterPointsMin;
const int maximum =
std::max(floorValue, std::min(kMeanFilterPointsMaxLimit, item.maxPoints));
item.points =
std::max(floorValue,
std::min(maximum,
floorValue +
static_cast<int>(std::lround(ratio * (maximum - floorValue)))));
}
else
{
@@ -1501,6 +1600,11 @@ namespace jdi
ratio * (maximum - kMeanFilterIterationsMin)))));
}
m_meanCurveCache.clear();
// 中心轴统计依赖参数 ⇒ 参数一变必须定向作废该信号的累积状态。
if (m_meanDragCenter)
{
InvalidateEdgeScansFor(ResolveSignal(m_meanDragRef).data, true);
}
m_dragMoved = true;
return true;
}
@@ -1639,14 +1743,14 @@ namespace jdi
}
case PlotDragMode::MEAN_SCROLL:
{
// 「均值滤波」面板内容滚动条:按滑块在轨道里的位置换算滚动偏移。
// 「均值滤波 / 中心轴边沿计数」面板内容滚动条:按滑块在轨道里的位置换算滚动偏移。
if (m_tab == nullptr || m_markerScrollDragLane < 0 ||
m_markerScrollDragLane >= static_cast<int>(m_tab->Lanes().size()))
{
break;
}
const EdgePanelLayout layout =
EdgePanelLayoutFor(m_markerScrollDragLane, EdgePanelKind::MEAN);
const EdgePanelLayout layout = EdgePanelLayoutFor(
m_markerScrollDragLane, m_meanDragCenter ? EdgePanelKind::CENTER : EdgePanelKind::MEAN);
const float travel = layout.scrollTrack.Height - layout.scrollThumb.Height;
const float maxScroll =
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
@@ -1659,7 +1763,11 @@ namespace jdi
std::min(static_cast<float>(y) - m_markerScrollGrabOffset,
layout.scrollTrack.Y + travel));
const float ratio = (thumbTop - layout.scrollTrack.Y) / travel;
if (SetLaneMeanFilterScroll(m_markerScrollDragLane, ratio * maxScroll))
const bool scrolled =
m_meanDragCenter
? SetLaneEdgeCenterScroll(m_markerScrollDragLane, ratio * maxScroll)
: SetLaneMeanFilterScroll(m_markerScrollDragLane, ratio * maxScroll);
if (scrolled)
{
m_dragMoved = true;
return true;
@@ -1827,17 +1935,18 @@ namespace jdi
hoverPanelKind);
}
// 「均值滤波 / 弱峰调整」面板**条目栏**的悬停 → 高亮该信号的预览曲线
//(用户 2026-10-30 要求:"鼠标处于条目栏上方时,对应的预览曲线需要高亮提示")。
// 「均值滤波 / 弱峰调整 / 中心轴边沿计数」面板**条目栏**的悬停 → 高亮该条目
//(用户 2026-10-30 要求:"鼠标处于条目栏上方时,对应的预览曲线需要高亮提示";
// 中心轴边沿计数自 2026-11-02 起同样有条目栏,条目本身也会叠一层强调色)。
{
int hoverLane = -1;
SignalRef hoverRef;
const EdgePanelKind hoverKind = static_cast<EdgePanelKind>(hoverPanelKind);
if (overEdgePanel && m_tab != nullptr && panelLane >= 0 &&
(static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::MEAN ||
static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::PEAK))
(hoverKind == EdgePanelKind::MEAN || hoverKind == EdgePanelKind::PEAK ||
hoverKind == EdgePanelKind::CENTER))
{
const EdgePanelLayout layout =
EdgePanelLayoutFor(panelLane, static_cast<EdgePanelKind>(hoverPanelKind));
const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, hoverKind);
if (UiPointInRect(layout.content, static_cast<float>(x), static_cast<float>(y)))
{
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
@@ -2236,14 +2345,29 @@ namespace jdi
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);
// 「均值滤波」:组头三角(展开 / 收起参数行)+ 两行参数滑块
//(轨道拖动 / 点击数值改上限 / 右侧复位按钮)。
// ⚠️ 所有命中都要求落在**内容区**之内:滚动到可视范围之外的行仍保留矩形,
// 若不加这条限制,它们会抢走底部「构建为新信号」按钮的点击。
if (meanMode)
if (listMode)
{
Lane& meanLane = m_tab->Lanes()[static_cast<std::size_t>(index)];
// 本面板的条目查找 / 创建(两者共用 `Lane::MeanFilterItem`)。
const auto findItem = [&meanLane, centerMode](const SignalRef& ref)
-> const Lane::MeanFilterItem* {
return centerMode ? meanLane.FindEdgeCenterItem(ref)
: meanLane.FindMeanFilterItem(ref);
};
const auto ensureItem = [&meanLane, centerMode](const SignalRef& ref)
-> Lane::MeanFilterItem& {
return centerMode ? meanLane.EnsureEdgeCenterItem(ref)
: meanLane.EnsureMeanFilterItem(ref);
};
// 内容区右侧的滚动条滑块:按下即进入拖动(与「自由标记」面板同一交互)。
if (layout.scrollThumb.Width > 0.0f &&
UiPointInRect(layout.scrollThumb, fx, fy))
@@ -2251,6 +2375,7 @@ namespace jdi
m_dragMode = PlotDragMode::MEAN_SCROLL;
m_markerScrollDragLane = index;
m_markerScrollGrabOffset = fy - layout.scrollThumb.Y;
m_meanDragCenter = centerMode;
return true;
}
const bool insideContent =
@@ -2267,10 +2392,10 @@ namespace jdi
const bool hasParams =
rowIndex < layout.rowLevelTracks.size() &&
layout.rowLevelTracks[rowIndex].Width > 0.0f &&
meanLane.FindMeanFilterItem(ref) != nullptr;
findItem(ref) != nullptr;
if (hasParams)
{
Lane::MeanFilterItem& item = meanLane.EnsureMeanFilterItem(ref);
Lane::MeanFilterItem& item = ensureItem(ref);
// 复位:均值点数 → 默认值(同时把滑块上限恢复为默认上限)。
if (rowIndex < layout.rowLevelResets.size() &&
layout.rowLevelResets[rowIndex].Width > 0.0f &&
@@ -2279,6 +2404,10 @@ namespace jdi
item.points = kMeanFilterPointsDefault;
item.maxPoints = kMeanFilterPointsDefaultMax;
m_meanCurveCache.clear();
if (centerMode)
{
InvalidateEdgeScansFor(ResolveSignal(ref).data, true);
}
m_dragMode = PlotDragMode::NONE;
return true;
}
@@ -2290,6 +2419,10 @@ namespace jdi
item.iterations = kMeanFilterIterationsMin;
item.maxIterations = kMeanFilterIterationsDefaultMax;
m_meanCurveCache.clear();
if (centerMode)
{
InvalidateEdgeScansFor(ResolveSignal(ref).data, true);
}
m_dragMode = PlotDragMode::NONE;
return true;
}
@@ -2300,6 +2433,7 @@ namespace jdi
{
m_editMeanRef = ref;
m_meanEditCurrentMax = item.maxPoints;
m_editMeanCenter = centerMode;
const gd::RectF& track = layout.rowLevelTracks[rowIndex];
const gd::RectF& value = layout.rowLevelValues[rowIndex];
const gd::RectF rowRect(track.X, value.Y,
@@ -2317,6 +2451,7 @@ namespace jdi
{
m_editMeanRef = ref;
m_meanEditCurrentMax = item.maxIterations;
m_editMeanCenter = centerMode;
const gd::RectF& track = layout.rowIterTracks[rowIndex];
const gd::RectF& value = layout.rowIterValues[rowIndex];
const gd::RectF rowRect(track.X, value.Y,
@@ -2331,21 +2466,23 @@ namespace jdi
if (rowIndex < layout.rowLevelTracks.size() &&
WeakPeakSliderHit(layout.rowLevelTracks[rowIndex], m_scale, fx, fy))
{
const int maximum = std::max(
kMeanFilterPointsMin,
std::min(kMeanFilterPointsMaxLimit, item.maxPoints));
const int floorValue = centerMode ? 0 : kMeanFilterPointsMin;
const int maximum =
std::max(floorValue, std::min(kMeanFilterPointsMaxLimit, item.maxPoints));
m_dragMode = PlotDragMode::MEAN_POINTS;
m_meanDragLane = index;
m_meanDragRef = ref;
m_meanDragCenter = centerMode;
const double ratio = UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx);
item.points = std::max(
kMeanFilterPointsMin,
floorValue,
std::min(maximum,
kMeanFilterPointsMin +
static_cast<int>(std::lround(
ratio *
(maximum - kMeanFilterPointsMin)))));
floorValue + static_cast<int>(std::lround(ratio * (maximum - floorValue)))));
m_meanCurveCache.clear();
if (centerMode)
{
InvalidateEdgeScansFor(ResolveSignal(ref).data, true);
}
return true;
}
// 拖动「迭代次数」滑块。
@@ -2358,15 +2495,19 @@ namespace jdi
m_dragMode = PlotDragMode::MEAN_ITERATIONS;
m_meanDragLane = index;
m_meanDragRef = ref;
m_meanDragCenter = centerMode;
const double ratio = UiSliderRatioFromX(layout.rowIterTracks[rowIndex], fx);
item.iterations = std::max(
kMeanFilterIterationsMin,
std::min(maximum,
kMeanFilterIterationsMin +
static_cast<int>(std::lround(
ratio *
(maximum - kMeanFilterIterationsMin)))));
ratio * (maximum - kMeanFilterIterationsMin)))));
m_meanCurveCache.clear();
if (centerMode)
{
InvalidateEdgeScansFor(ResolveSignal(ref).data, true);
}
return true;
}
}
@@ -2385,11 +2526,18 @@ namespace jdi
rowIndex < layout.rowChecks.size() &&
layout.rowChecks[rowIndex].Width > 0.0f &&
UiPointInRect(layout.rowChecks[rowIndex], fx, fy);
Lane::MeanFilterItem* existing = const_cast<Lane::MeanFilterItem*>(
meanLane.FindMeanFilterItem(ref));
Lane::MeanFilterItem* existing =
const_cast<Lane::MeanFilterItem*>(findItem(ref));
if (onCheckBox)
{
ToggleLaneMeanFilterSignal(index, ref);
if (centerMode)
{
ToggleLaneEdgeCenterSignal(index, ref);
}
else
{
ToggleLaneMeanFilterSignal(index, ref);
}
m_meanRowHoverLane = -1;
m_meanRowHoverRef = SignalRef();
}
@@ -3057,9 +3205,10 @@ namespace jdi
// 弱峰滑块拖拽结束:清掉目标(避免留下上一轮的信号引用)。
m_peakDragLane = -1;
m_peakDragRef = SignalRef();
// 「均值滤波」参数滑块拖拽结束:同样清掉目标。
// 「均值滤波 / 中心轴边沿计数」参数滑块拖拽结束:同样清掉目标。
m_meanDragLane = -1;
m_meanDragRef = SignalRef();
m_meanDragCenter = false;
// 「标记透明度」滑块 / 面板内容滚动条拖拽结束。
m_markerOpacityDragLane = -1;
m_markerScrollDragLane = -1;
@@ -3111,10 +3260,11 @@ namespace jdi
panelLane, panelKind))
{
const EdgePanelKind hoveredKind = static_cast<EdgePanelKind>(panelKind);
// 「均值滤波」面板:内容区超高时用滚轮翻页(上百个信号合并时必备)。
if (hoveredKind == EdgePanelKind::MEAN)
// 「均值滤波 / 中心轴边沿计数」面板:内容区超高时用滚轮翻页
//(上百个信号合并时必备)。
if (hoveredKind == EdgePanelKind::MEAN || hoveredKind == EdgePanelKind::CENTER)
{
const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, EdgePanelKind::MEAN);
const EdgePanelLayout layout = EdgePanelLayoutFor(panelLane, hoveredKind);
if (layout.content.Width > 0.0f &&
UiPointInRect(layout.content, static_cast<float>(m_mouseX),
static_cast<float>(m_mouseY)))
@@ -3123,11 +3273,21 @@ namespace jdi
std::max(0.0f, layout.markerContentHeight - layout.content.Height);
if (maxScroll > 0.0f)
{
const float current =
Tab().Lanes()[static_cast<std::size_t>(panelLane)].meanFilterScroll;
const Lane& wheelLane =
Tab().Lanes()[static_cast<std::size_t>(panelLane)];
const float current = (hoveredKind == EdgePanelKind::CENTER)
? wheelLane.edgeCenterScroll
: wheelLane.meanFilterScroll;
const float next = current - static_cast<float>(notches * 40.0 * m_scale);
SetLaneMeanFilterScroll(panelLane,
std::max(0.0f, std::min(next, maxScroll)));
const float target = std::max(0.0f, std::min(next, maxScroll));
if (hoveredKind == EdgePanelKind::CENTER)
{
SetLaneEdgeCenterScroll(panelLane, target);
}
else
{
SetLaneMeanFilterScroll(panelLane, target);
}
return true;
}
}