弱峰增强改回逐信号滑块;迭代次数上限可就地编辑
一、参数输入方式(回到滑块形态,但每个信号各有一套) - 面板布局:每个被勾选的信号 = 名称行(+复选框)+ 两行滑块: 「增强等级 %」(0~200%)与「迭代次数 N」(1 ~ 该信号自己的上限)。 EdgePanelLayout 新增逐信号矩形 rowLevelTracks/rowLevelValues/rowIterTracks/rowIterValues; PEAK 的 rowPoints/rowIterations 恒为空(MEAN/CENTER 仍用文本输入框)。 - 恢复 kWeakPeakSliderRowHeight/kWeakPeakTrackHeight/kWeakPeakTrackMinWidth 与 WeakPeakSliderHit();恢复 PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS, 并新增 m_peakDragRef(拖动时必须知道是哪个信号)。 - 列宽算式集中在 WeakPeakLabelColumnWidth()/WeakPeakValueColumnWidth(), 与 EdgePanelWidth 共用,避免布局与面板宽度计算漂移。 二、迭代次数滑块上限可就地编辑(用户要求的行为) - 点击「迭代次数 N」滑块右侧的数值文本 → 整条滑块行(轨道 + 数值)变成一个输入框; 左侧标签保留可见。 - 输入框以空内容开始;空内容时以灰色提示当前上限值(m_peakEditCurrentMax,EndEdit 清零)。 - 过滤器为 EditFilter::BYTE(纯数字):负号、小数点、字母无法键入,粘贴同样被清洗, 从源头满足"只允许输入正整数"。 - 提交(回车 / 点击别处):仅当解析成功且 1 <= 值 <= 1000 时才作为新上限; 空内容、0、超范围一律保持原上限不变;生效后若当前迭代次数超过新上限则自动替换为新上限。 - 输入框在 paintOverlays() 中与 Y 轴上下限输入框一起绘制(覆盖在面板之上)。 - 新增 PlotEditTarget::PEAK_ITERATIONS_MAX;删除 PEAK_LEVEL_PERCENT/PEAK_ITERATIONS (等级只能拖滑块,迭代次数只能拖滑块)。 三、数据与缓存 - Lane::WeakPeakItem 新增 maxIterations(默认 10)。 - maxIterations 参与 PanelSlotKey(决定圆钮位置,面板必须重画), 但不参与 SceneContentHash(不影响网格内曲线,不应触发波形重画)。 四、测试 - uitest [X] 改为:滑块存在性/层序/列宽、等级滑块拖动(中间=100%、右端=200%)、 迭代滑块拖动(左端=1、右端=当前上限)、点击数值进入上限编辑(空内容 + 提示值 + 输入框 横跨整行、空内容回车不改、'-'/字母被拒、输入 40 生效且右端变 40、下调到 4 时当前值被夹到 4、 输入 0 被拒、点击别处提交),并保存编辑态截图供目视核对。 - make all 零警告;uitest 409/409 通过;panperf PASS;layercheck 保持像素一致。
This commit is contained in:
1 parent
1ddbc37b78
commit
a30a6fcf4d
4 files changed
+598
-112
No files matched your search
@@ -43,6 +43,8 @@ namespace jdi
|
||||
MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */
|
||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||
PEAK_LEVEL, /**< 拖动「弱峰增强」某信号的等级滑块(0–200%)。 */
|
||||
PEAK_ITERATIONS, /**< 拖动「弱峰增强」某信号的迭代次数滑块(1 … 该信号自定义上限)。 */
|
||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||
};
|
||||
|
||||
@@ -71,8 +73,7 @@ namespace jdi
|
||||
EDGE_CENTER_ITERATIONS, /**< 编辑「中心轴边沿计数」的迭代次数 N。 */
|
||||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||||
MEAN_FILTER_ITERATIONS, /**< 编辑「均值滤波」的迭代次数 N。 */
|
||||
PEAK_LEVEL_PERCENT, /**< 编辑「弱峰增强」某信号的增强等级(0–200%)。 */
|
||||
PEAK_ITERATIONS /**< 编辑「弱峰增强」某信号的迭代次数 N。 */
|
||||
PEAK_ITERATIONS_MAX /**< 编辑「弱峰增强」某信号「迭代次数 N」滑块的**上限**(正整数)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1133,15 +1134,26 @@ namespace jdi
|
||||
* @brief 「逐条目」面板的矩形(MEAN / CENTER / PEAK 面板共用同一套排布)。
|
||||
*
|
||||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为第二排的两个输入框
|
||||
*(PEEK 信号分别为「增强等级 %」与「迭代次数 N」);
|
||||
* `rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为**输入框**
|
||||
*(MEAN / CENTER 使用);`rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* 未勾选的条目对应的矩形均为空。
|
||||
*/
|
||||
std::vector<gd::RectF> rowChecks;
|
||||
std::vector<gd::RectF> rowPoints;
|
||||
std::vector<gd::RectF> rowIterations;
|
||||
std::vector<gd::RectF> rowStats;
|
||||
/**
|
||||
* @brief 「弱峰增强」每个被勾选信号自己的两行**滑块**(与 `rowRefs` 同序)。
|
||||
*
|
||||
* `rowLevelTracks/rowLevelValues` = 增强等级滑块与它右侧的数值文本;
|
||||
* `rowIterTracks/rowIterValues` = 迭代次数滑块与它右侧的数值文本
|
||||
*(**点值数值文本可以把滑块上限改为别的正整数**)。
|
||||
* 未勾选或非 PEAK 面板时对应元素为空矩形(数组也可能为空)。
|
||||
*/
|
||||
std::vector<gd::RectF> rowLevelTracks;
|
||||
std::vector<gd::RectF> rowLevelValues;
|
||||
std::vector<gd::RectF> rowIterTracks;
|
||||
std::vector<gd::RectF> rowIterValues;
|
||||
std::vector<SignalRef> rowRefs;
|
||||
};
|
||||
|
||||
@@ -1153,6 +1165,14 @@ namespace jdi
|
||||
*/
|
||||
EdgePanelLayout EdgePanelLayoutFor(int laneIndex, EdgePanelKind kind) const;
|
||||
|
||||
/**
|
||||
* @brief 在面板布局里找出某个信号对应的「逐条目」行下标。
|
||||
* @param [IN] layout 面板布局。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 行下标(与 `rowRefs` 同序);未找到返回 -1。
|
||||
*/
|
||||
int PeakRowIndexOf(const EdgePanelLayout& layout, const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 计算单个面板的缓存键(全局键之外,面板自身的内容)。
|
||||
* @param [IN] laneIndex 面板所属网格。
|
||||
@@ -1658,6 +1678,14 @@ namespace jdi
|
||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<WeakPeakBuild> m_pendingWeakPeakItems; /**< 待构建的弱峰增强信号及其参数。 */
|
||||
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||||
int m_peakDragLane = -1; /**< 正在拖动弱峰滑块的面板所属网格。 */
|
||||
SignalRef m_peakDragRef; /**< 正在拖动弱峰滑块的**信号**(每个信号一套参数)。 */
|
||||
/**
|
||||
* @brief 正在编辑「迭代次数 N 上限」时,输入框内灰色提示用的**当前上限值**。
|
||||
*
|
||||
* 输入框以空内容开始,空内容时显示这个值作为提示;进入编辑时记录,提交后清零。
|
||||
*/
|
||||
int m_peakEditCurrentMax = 0;
|
||||
/**
|
||||
* @brief 本帧是否使用「快速绘制」(交互期间关闭波形与派生曲线的抗锯齿)。
|
||||
*
|
||||
|
||||
@@ -68,8 +68,14 @@ namespace jdi
|
||||
const float kEdgePanelMeanEditHeight = 24.0f; /**< 逐条目面板中每条信号的参数行高度。 */
|
||||
const float kEdgePanelMeanRowGap = 2.0f; /**< 名称行与参数行之间的间距。 */
|
||||
const float kEdgePanelMeanCheckSize = 15.0f; /**< 逐条目面板中条目复选框尺寸。 */
|
||||
const float kWeakPeakSliderRowHeight = 26.0f; /**< 弱峰增强面板中每个滑块行的高度。 */
|
||||
const float kWeakPeakTrackHeight = 6.0f; /**< 弱峰增强滑块的轨道高度。 */
|
||||
const float kWeakPeakTrackMinWidth = 120.0f; /**< 弱峰增强滑块轨道的最小宽度。 */
|
||||
const int kWeakPeakIterationsMin = 1; /**< 迭代次数 N 的下限。 */
|
||||
const int kWeakPeakIterationsMax = 10; /**< 迭代次数 N 的上限。 */
|
||||
/** 迭代次数滑块「自定义上限」的允许范围(与算法内部的阶数夹取保持一致)。 */
|
||||
const int kWeakPeakIterationsMaxLimit = 1000;
|
||||
/** 迭代次数滑块上限的默认值(与 `Lane::WeakPeakItem::maxIterations` 一致)。 */
|
||||
const int kWeakPeakIterationsDefaultMax = 10;
|
||||
/** 弱峰增强等级上限(面板上显示为 200%)。 */
|
||||
const double kWeakPeakLevelMax = 2.0;
|
||||
/** 弱峰增强等级百分比上限(用于构建新信号 / 曲线缓存键)。 */
|
||||
@@ -95,12 +101,56 @@ namespace jdi
|
||||
* 此时改为交后台线程计算、本帧先用旧曲线顶替(双缓冲),保证控件反馈始终跟手。
|
||||
*/
|
||||
const std::size_t kDerivedAsyncWindowThreshold = 65536;
|
||||
const wchar_t* const kWeakPeakLevelLabel = L"增强等级 %"; /**< 逐条目面板里「等级」输入框的标签。 */
|
||||
const wchar_t* const kWeakPeakIterationsLabel = L"迭代次数 N"; /**< 逐条目面板里「迭代次数」输入框的标签。 */
|
||||
const wchar_t* const kWeakPeakLevelLabel = L"增强等级 %"; /**< 逐条目面板里「等级」滑块行的标签。 */
|
||||
const wchar_t* const kWeakPeakIterationsLabel = L"迭代次数 N"; /**< 逐条目面板里「迭代次数」滑块行的标签。 */
|
||||
const wchar_t* const kWeakPeakTitle = L"弱峰增强"; /**< 弱峰增强面板标题。 */
|
||||
const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱峰增强共用)。 */
|
||||
const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */
|
||||
|
||||
/**
|
||||
* @brief 判断坐标是否命中「弱峰增强」的某条滑块轨道。
|
||||
*
|
||||
* 轨道本身只有几个像素高,直接按轨道矩形命中很难抓住,因此把命中区在纵向上下各放宽
|
||||
* (至少一个轨道高度),与样式区滑块的交互一致。
|
||||
*
|
||||
* @param [IN] track 滑块轨道矩形(宽度为 0 表示当前没有滑块)。
|
||||
* @param [IN] scale DPI 缩放比例。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @return 命中返回 true。
|
||||
*/
|
||||
bool WeakPeakSliderHit(const gd::RectF& track, float scale, float x, float y)
|
||||
{
|
||||
if (track.Width <= 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
gd::RectF hit = track;
|
||||
const float pad = std::max(8.0f * scale, track.Height);
|
||||
hit.Y -= pad;
|
||||
hit.Height += pad * 2.0f;
|
||||
return UiPointInRect(hit, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 「弱峰增强」滑块行右侧**数值列**的宽度(布局与面板宽度共用同一算式)。
|
||||
*
|
||||
* 取「200%」与「10000」两者较宽者:前者是等级滑块的最大显示文本,
|
||||
* 后者为迭代次数上限留出足够位宽(上限可被用户改大)。
|
||||
*/
|
||||
float WeakPeakValueColumnWidth(HFONT monoSmall)
|
||||
{
|
||||
return std::max(UiMeasureTextWidth(monoSmall, L"200%"),
|
||||
UiMeasureTextWidth(monoSmall, L"10000"));
|
||||
}
|
||||
|
||||
/** @brief 「弱峰增强」滑块行左侧**标签列**的宽度(两条标签取较宽者,保证轨道左对齐)。 */
|
||||
float WeakPeakLabelColumnWidth(HFONT uiSmall)
|
||||
{
|
||||
return std::max(UiMeasureTextWidth(uiSmall, kWeakPeakLevelLabel),
|
||||
UiMeasureTextWidth(uiSmall, kWeakPeakIterationsLabel));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取折线(相邻采样点之间线性连接)在给定时刻的取值。
|
||||
*
|
||||
@@ -2468,7 +2518,7 @@ namespace jdi
|
||||
int levelPercent, int iterations) const
|
||||
{
|
||||
const int level = std::max(0, std::min(kWeakPeakPercentMax, levelPercent));
|
||||
const int stages = std::max(kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMax, iterations));
|
||||
const int stages = std::max(kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMaxLimit, iterations));
|
||||
for (const WeakPeakCurveEntry& entry : m_weakPeakCurveCache)
|
||||
{
|
||||
if (entry.signal == &signal && entry.levelPercent == level && entry.iterations == stages)
|
||||
@@ -2861,13 +2911,11 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + std::max(nameWidth, countWidth));
|
||||
}
|
||||
|
||||
if (centerAxis || meanFilter || weakPeak)
|
||||
if (centerAxis || meanFilter)
|
||||
{
|
||||
// 参数行 = 两个「说明文字 + 数值框」,必须保证文字不被数值框压住。
|
||||
// (MEAN / CENTER:「均值点数 X」+「迭代次数 N」;PEAK:「增强等级 %」+「迭代次数 N」。
|
||||
// 标签与数值框各占半行。)
|
||||
const wchar_t* const labelX = weakPeak ? kWeakPeakLevelLabel : L"均值点数 X";
|
||||
const float labelXWidth = UiMeasureTextWidth(m_fonts->uiSmall, labelX);
|
||||
// (MEAN / CENTER:「均值点数 X」+「迭代次数 N」。标签与数值框各占半行。)
|
||||
const float labelXWidth = UiMeasureTextWidth(m_fonts->uiSmall, L"均值点数 X");
|
||||
const float labelN = UiMeasureTextWidth(m_fonts->uiSmall, L"迭代次数 N");
|
||||
const float fieldWidth =
|
||||
std::max(kEdgePanelEditFieldWidth * m_scale * 0.75f,
|
||||
@@ -2877,6 +2925,15 @@ namespace jdi
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||
}
|
||||
|
||||
if (weakPeak)
|
||||
{
|
||||
// 滑块行 = 「标签列 + 轨道(最小宽度)+ 数值列」(等级与迭代次数两行共用同一列宽)。
|
||||
const float rowWidth = WeakPeakLabelColumnWidth(m_fonts->uiSmall) + 6.0f * m_scale +
|
||||
kWeakPeakTrackMinWidth * m_scale + 6.0f * m_scale +
|
||||
WeakPeakValueColumnWidth(m_fonts->monoSmall);
|
||||
width = std::max(width, kEdgePanelPad * 2.0f * m_scale + rowWidth);
|
||||
}
|
||||
|
||||
if (meanFilter || weakPeak)
|
||||
{
|
||||
// 底部按钮行 = 「构建为新信号」,按按钮文字实测宽度确定下限。
|
||||
@@ -3405,11 +3462,10 @@ namespace jdi
|
||||
m_editTarget == PlotEditTarget::EDGE_CENTER_ITERATIONS ||
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_POINTS ||
|
||||
m_editTarget == PlotEditTarget::MEAN_FILTER_ITERATIONS ||
|
||||
m_editTarget == PlotEditTarget::PEAK_LEVEL_PERCENT ||
|
||||
m_editTarget == PlotEditTarget::PEAK_ITERATIONS)
|
||||
m_editTarget == PlotEditTarget::PEAK_ITERATIONS_MAX)
|
||||
{
|
||||
// 「均值点数 X」/「迭代次数 N」(均值滤波、中心轴)与「增强等级 %」/「迭代次数 N」(弱峰增强):
|
||||
// 输入框都落在对应的字段矩形上(三类面板的字段矩形含义相同)。
|
||||
// 「均值点数 X」/「迭代次数 N」(均值滤波、中心轴)与「迭代次数 N 上限」(弱峰增强):
|
||||
// 输入框都落在对应的字段/滑块行矩形上。
|
||||
if (m_editFieldRect.Width <= 0.0f)
|
||||
{
|
||||
return gd::RectF();
|
||||
@@ -3464,7 +3520,6 @@ namespace jdi
|
||||
}
|
||||
m_editTarget = target;
|
||||
m_editLane = laneIndex;
|
||||
|
||||
// 名称与数值均不限长度(数值在提交时会做范围夹取)。
|
||||
const bool nameField = (target == PlotEditTarget::SIGNAL_NAME);
|
||||
m_edit.Begin(initialText, true, nameField ? EditFilter::ANY : EditFilter::NUMBER, 0u);
|
||||
@@ -3501,6 +3556,7 @@ namespace jdi
|
||||
m_editLane = -1;
|
||||
m_editSignal = SignalRef();
|
||||
m_editFieldRect = gd::RectF();
|
||||
m_peakEditCurrentMax = 0; // 退出编辑:清除“迭代上限”输入框的灰色提示值
|
||||
m_edit.End();
|
||||
|
||||
// 信号改名:交给宿主窗口落到数据层,绘图区只持有只读数据。
|
||||
@@ -3575,12 +3631,12 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
// 「均值点数 X」/「迭代次数 N」:作用于被勾选信号的均值滤波曲线,夹在合理范围内。
|
||||
// 「中心轴边沿计数」用**完全相同的参数与语义**,只是作用于自己的条目列表。
|
||||
const bool centerTarget =
|
||||
(target == PlotEditTarget::EDGE_CENTER_POINTS || target == PlotEditTarget::EDGE_CENTER_ITERATIONS);
|
||||
// 「弱峰增强」也是逐信号参数(增强等级 % + 迭代次数 N),只是作用于自己的条目列表。
|
||||
if (target == PlotEditTarget::PEAK_LEVEL_PERCENT || target == PlotEditTarget::PEAK_ITERATIONS)
|
||||
// 「弱峰增强」迭代次数滑块的**上限**:用户可直接输入一个正整数作为新的量程上限。
|
||||
// 规则(用户明确要求):
|
||||
// * 输入框只允许输入正整数(过滤策略为 BYTE = 纯数字,粘贴同样会被清洗);
|
||||
// * 空内容 / 非法值(0、超出允许范围)→ 保持原上限不变;
|
||||
// * 生效后若当前迭代次数超过新上限,则自动夹到新上限。
|
||||
if (target == PlotEditTarget::PEAK_ITERATIONS_MAX)
|
||||
{
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(laneIndex)];
|
||||
const Lane::WeakPeakItem* existing = lane.FindWeakPeakItem(m_editMeanRef);
|
||||
@@ -3588,21 +3644,27 @@ namespace jdi
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Lane::WeakPeakItem& item = lane.EnsureWeakPeakItem(m_editMeanRef);
|
||||
if (target == PlotEditTarget::PEAK_LEVEL_PERCENT)
|
||||
if (parsedOk)
|
||||
{
|
||||
// 增强等级 0–200%。
|
||||
item.levelPercent = std::max(0, std::min(kWeakPeakPercentMax, static_cast<int>(std::lround(parsed))));
|
||||
const long long value = static_cast<long long>(std::llround(parsed));
|
||||
if (value >= kWeakPeakIterationsMin && value <= kWeakPeakIterationsMaxLimit)
|
||||
{
|
||||
Lane::WeakPeakItem& item = lane.EnsureWeakPeakItem(m_editMeanRef);
|
||||
item.maxIterations = static_cast<int>(value);
|
||||
// 当前值超出新上限 → 自动替换为上界。
|
||||
item.iterations = std::max(kWeakPeakIterationsMin,
|
||||
std::min(item.maxIterations, item.iterations));
|
||||
// 曲线可能因迭代次数被夹取而变化,缓存整体作废最稳妥。
|
||||
m_weakPeakCurveCache.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.iterations = std::max(kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMax, static_cast<int>(std::lround(parsed))));
|
||||
}
|
||||
// 参数变化会改变曲线:清掉曲线缓存即可(局部重画由 `m_sceneDerived` 快照驱动)。
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 「均值点数 X」/「迭代次数 N」:作用于被勾选信号的均值滤波曲线,夹在合理范围内。
|
||||
// 「中心轴边沿计数」用**完全相同的参数与语义**,只是作用于自己的条目列表。
|
||||
const bool centerTarget =
|
||||
(target == PlotEditTarget::EDGE_CENTER_POINTS || target == PlotEditTarget::EDGE_CENTER_ITERATIONS);
|
||||
if (target == PlotEditTarget::MEAN_FILTER_POINTS || target == PlotEditTarget::MEAN_FILTER_ITERATIONS ||
|
||||
centerTarget)
|
||||
{
|
||||
@@ -3830,6 +3892,10 @@ namespace jdi
|
||||
case PlotDragMode::CURSOR_SPAN_MOVE:
|
||||
// 差值标签拖拽:两条光标整体沿时间轴平移 → 左右箭头。
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
case PlotDragMode::PEAK_LEVEL:
|
||||
case PlotDragMode::PEAK_ITERATIONS:
|
||||
// 「弱峰增强」滑块拖拽 → 左右箭头。
|
||||
return LoadCursorW(nullptr, IDC_SIZEWE);
|
||||
case PlotDragMode::MOVE_SIGNAL:
|
||||
// 拖动信号(参考 Photoshop 平移元素的抓握光标)。
|
||||
return LoadCursorW(nullptr, IDC_SIZEALL);
|
||||
@@ -3880,6 +3946,31 @@ namespace jdi
|
||||
// 「构建为新信号」按钮:手型光标作为可点击提示。
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
// 「弱峰增强」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。
|
||||
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.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;
|
||||
}
|
||||
@@ -4121,6 +4212,70 @@ namespace jdi
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
case PlotDragMode::PEAK_LEVEL:
|
||||
{
|
||||
// 「弱峰增强」某信号的等级滑块:按滑轨横向位置换算 0–200%。
|
||||
if (m_tab == nullptr || m_peakDragLane < 0 ||
|
||||
m_peakDragLane >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(m_peakDragLane, EdgePanelKind::PEAK);
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(m_peakDragLane)];
|
||||
if (lane.FindWeakPeakItem(m_peakDragRef) == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Lane::WeakPeakItem& item = lane.EnsureWeakPeakItem(m_peakDragRef);
|
||||
const int rowIndex = PeakRowIndexOf(layout, m_peakDragRef);
|
||||
if (rowIndex < 0 || rowIndex >= static_cast<int>(layout.rowLevelTracks.size()) ||
|
||||
layout.rowLevelTracks[static_cast<std::size_t>(rowIndex)].Width <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const double ratio =
|
||||
UiSliderRatioFromX(layout.rowLevelTracks[static_cast<std::size_t>(rowIndex)], fx);
|
||||
item.levelPercent =
|
||||
std::max(0, std::min(kWeakPeakPercentMax,
|
||||
static_cast<int>(std::lround(ratio * 100.0 * kWeakPeakLevelMax))));
|
||||
m_weakPeakCurveCache.clear();
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
case PlotDragMode::PEAK_ITERATIONS:
|
||||
{
|
||||
// 「弱峰增强」某信号的迭代次数滑块:量程为 1 … 该信号自己的上限。
|
||||
if (m_tab == nullptr || m_peakDragLane < 0 ||
|
||||
m_peakDragLane >= static_cast<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const EdgePanelLayout layout = EdgePanelLayoutFor(m_peakDragLane, EdgePanelKind::PEAK);
|
||||
Lane& lane = m_tab->Lanes()[static_cast<std::size_t>(m_peakDragLane)];
|
||||
if (lane.FindWeakPeakItem(m_peakDragRef) == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Lane::WeakPeakItem& item = lane.EnsureWeakPeakItem(m_peakDragRef);
|
||||
const int rowIndex = PeakRowIndexOf(layout, m_peakDragRef);
|
||||
if (rowIndex < 0 || rowIndex >= static_cast<int>(layout.rowIterTracks.size()) ||
|
||||
layout.rowIterTracks[static_cast<std::size_t>(rowIndex)].Width <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const double ratio =
|
||||
UiSliderRatioFromX(layout.rowIterTracks[static_cast<std::size_t>(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();
|
||||
m_dragMoved = true;
|
||||
return true;
|
||||
}
|
||||
case PlotDragMode::MOVE_SIGNAL:
|
||||
{
|
||||
const int target = LaneAtY(y);
|
||||
@@ -4433,7 +4588,7 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 「中心轴边沿计数」/「均值滤波」/「弱峰增强」面板:逐信号条目
|
||||
// (同一套「名称 + 复选框 + 两个参数输入框」排布,只是绑定的条目列表不同)。
|
||||
// (前两者是「名称 + 复选框 + 两个参数输入框」,弱峰增强是「名称 + 复选框 + 两行滑块」)。
|
||||
if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER ||
|
||||
kind == EdgePanelKind::PEAK) &&
|
||||
m_tab != nullptr)
|
||||
@@ -4442,6 +4597,78 @@ namespace jdi
|
||||
const bool boostMode = (kind == EdgePanelKind::PEAK);
|
||||
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);
|
||||
// 迭代次数行右侧的**数值文本**:点击后整行变成输入框以修改滑块上限。
|
||||
// ⚠️ 必须放在轨道命中之前(数值区在轨道右侧,命中区不重叠,但顺序更安全)。
|
||||
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;
|
||||
item.levelPercent = static_cast<int>(std::lround(
|
||||
UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 100.0 *
|
||||
kWeakPeakLevelMax));
|
||||
item.levelPercent =
|
||||
std::max(0, std::min(kWeakPeakPercentMax, item.levelPercent));
|
||||
m_weakPeakCurveCache.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
const SignalRef& ref = layout.rowRefs[rowIndex];
|
||||
@@ -4469,52 +4696,32 @@ namespace jdi
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
// 第二排的两个输入框 → 就地编辑该信号的参数
|
||||
//(MEAN/CENTER:均值点数 X / 迭代次数 N;PEAK:增强等级 % / 迭代次数 N)。
|
||||
// 第二排的两个输入框 → 就地编辑该信号的参数(仅 MEAN / CENTER 面板;
|
||||
// 弱峰增强用的是滑块,已在上面处理)。
|
||||
if (layout.rowPoints[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowPoints[rowIndex], fx, fy))
|
||||
{
|
||||
m_editMeanRef = ref;
|
||||
if (boostMode)
|
||||
{
|
||||
const Lane::WeakPeakItem* item = tabLane.FindWeakPeakItem(ref);
|
||||
BeginEdit(PlotEditTarget::PEAK_LEVEL_PERCENT, index,
|
||||
std::to_wstring(item != nullptr ? item->levelPercent : 150),
|
||||
layout.rowPoints[rowIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
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]);
|
||||
}
|
||||
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;
|
||||
if (boostMode)
|
||||
{
|
||||
const Lane::WeakPeakItem* item = tabLane.FindWeakPeakItem(ref);
|
||||
BeginEdit(PlotEditTarget::PEAK_ITERATIONS, index,
|
||||
std::to_wstring(item != nullptr ? item->iterations : 1),
|
||||
layout.rowIterations[rowIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
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]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4542,9 +4749,12 @@ namespace jdi
|
||||
build.signalIndex = ref.signalIndex;
|
||||
build.levelPercent =
|
||||
std::max(0, std::min(kWeakPeakPercentMax, item->levelPercent));
|
||||
build.iterations = std::max(
|
||||
// 迭代次数按**该信号自己的滑块量程**(1 … `maxIterations`)夹取。
|
||||
const int peakMax = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMax, item->iterations));
|
||||
std::min(kWeakPeakIterationsMaxLimit, item->maxIterations));
|
||||
build.iterations =
|
||||
std::max(kWeakPeakIterationsMin, std::min(peakMax, item->iterations));
|
||||
m_pendingWeakPeakItems.push_back(build);
|
||||
}
|
||||
if (!m_pendingWeakPeakItems.empty())
|
||||
@@ -4810,6 +5020,9 @@ namespace jdi
|
||||
m_edgeDragLane = -1;
|
||||
m_edgePanelDragLane = -1;
|
||||
m_edgePanelDragKind = EdgePanelKind::COUNT;
|
||||
// 弱峰滑块拖拽结束:清掉目标(避免留下上一轮的信号引用)。
|
||||
m_peakDragLane = -1;
|
||||
m_peakDragRef = SignalRef();
|
||||
// 松开左键:粘滞提示结束计时,开始"停留后淡出"。
|
||||
ReleaseHint();
|
||||
return changed;
|
||||
@@ -5411,9 +5624,11 @@ namespace jdi
|
||||
PaintEdgeCursor(g, m_lanes);
|
||||
PaintSignalInteraction(g);
|
||||
PaintHint(g);
|
||||
// 网格 Y 轴上下限的输入框绘制在最上层。
|
||||
// 网格 Y 轴上下限的输入框、以及「弱峰增强 → 迭代次数上限」的输入框都绘制在最上层
|
||||
//(后者覆盖在浮动面板的滑块行之上)。
|
||||
if (m_editTarget == PlotEditTarget::LANE_Y_MAX ||
|
||||
m_editTarget == PlotEditTarget::LANE_Y_MIN)
|
||||
m_editTarget == PlotEditTarget::LANE_Y_MIN ||
|
||||
m_editTarget == PlotEditTarget::PEAK_ITERATIONS_MAX)
|
||||
{
|
||||
PaintEditField(g);
|
||||
}
|
||||
@@ -7847,7 +8062,8 @@ namespace jdi
|
||||
const int levelPercent =
|
||||
std::max(0, std::min(kWeakPeakPercentMax, item->levelPercent));
|
||||
const int boostIterations = std::max(
|
||||
kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMax, item->iterations));
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit, item->iterations));
|
||||
PaintWeakPeakCurve(g, resolved, lane, levelPercent, boostIterations);
|
||||
}
|
||||
}
|
||||
@@ -8643,7 +8859,7 @@ namespace jdi
|
||||
std::max(0, std::min(kWeakPeakPercentMax, boostItem->levelPercent));
|
||||
const int iterations = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMax, boostItem->iterations));
|
||||
std::min(kWeakPeakIterationsMaxLimit, boostItem->iterations));
|
||||
paintDerivedCapsule(DerivedCurveKind::WEAK_PEAK, 0, iterations, levelPercent,
|
||||
L"弱峰增强");
|
||||
}
|
||||
@@ -8867,11 +9083,11 @@ namespace jdi
|
||||
const float rowsPerSignal = meanFilter ? 1.0f : 2.0f;
|
||||
|
||||
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)
|
||||
// + 第二排(均值点数 X / 迭代次数 N,或弱峰增强的增强等级 % / 迭代次数 N)
|
||||
// +(中心轴)第三排(统计结果);
|
||||
// 后两排**只在该条目被勾选时才占位**,因此面板高度随勾选数量动态变化。
|
||||
// 逐条目参数行:MEAN / CENTER / PEAK 三种面板共用(PEEK 与 MEAN 完全同构)。
|
||||
// + 参数部分(**只在该条目被勾选时才占位**,因此面板高度随勾选数量动态变化):
|
||||
// * MEAN / CENTER:一行「均值点数 X + 迭代次数 N」输入框(中心轴再多一行统计结果);
|
||||
// * PEAK:两行滑块(增强等级 % / 迭代次数 N),每个信号各有一套自己的参数。
|
||||
const bool itemHasParams = (centerAxis || meanFilter || weakPeak);
|
||||
const float weakPeakSliderRow = kWeakPeakSliderRowHeight * m_scale;
|
||||
float meanRowHeight = 0.0f;
|
||||
float itemsHeight = 0.0f;
|
||||
float height = 0.0f;
|
||||
@@ -8891,10 +9107,17 @@ namespace jdi
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (checked && itemHasParams)
|
||||
{
|
||||
itemsHeight += (kEdgePanelMeanEditHeight + kEdgePanelMeanRowGap) * m_scale;
|
||||
if (centerAxis)
|
||||
if (weakPeak)
|
||||
{
|
||||
itemsHeight += kEdgePanelMeanRowHeight * m_scale;
|
||||
itemsHeight += weakPeakSliderRow * 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemsHeight += (kEdgePanelMeanEditHeight + kEdgePanelMeanRowGap) * m_scale;
|
||||
if (centerAxis)
|
||||
{
|
||||
itemsHeight += kEdgePanelMeanRowHeight * m_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8946,12 +9169,19 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 逐信号条目:第一排 = 名称 + 复选框(复选框靠右);
|
||||
// 第二排 = 两个参数输入框(MEAN:均值点数 X / 迭代次数 N;PEAK:增强等级 % / 迭代次数 N,
|
||||
// 仅勾选时存在;
|
||||
// 第三排(中心轴)= 上升沿/下降沿统计结果(仅勾选时存在)。
|
||||
// 参数部分:MEAN / CENTER = 一行两个输入框(中心轴再多一行统计结果);
|
||||
// PEAK = 两行滑块(每个信号各有自己的等级与迭代次数)。
|
||||
const float checkSize = kEdgePanelMeanCheckSize * m_scale;
|
||||
const float fieldHeight = kEdgePanelMeanEditHeight * m_scale;
|
||||
const float halfWidth = std::max(40.0f, (width - pad * 2.0f - 8.0f * m_scale) * 0.5f);
|
||||
// 滑块行列宽(与 `EdgePanelWidth` 使用同一组算式,避免布局与宽度计算漂移)。
|
||||
const float weakPeakLabelWidth = WeakPeakLabelColumnWidth(m_fonts->uiSmall);
|
||||
const float weakPeakValueWidth = WeakPeakValueColumnWidth(m_fonts->monoSmall);
|
||||
const float weakPeakTrackHeight = kWeakPeakTrackHeight * m_scale;
|
||||
const float weakPeakTrackLeft = left + pad + weakPeakLabelWidth + 6.0f * m_scale;
|
||||
const float weakPeakTrackWidth =
|
||||
std::max(10.0f * m_scale, (left + width - pad - weakPeakValueWidth - 6.0f * m_scale) -
|
||||
weakPeakTrackLeft);
|
||||
float rowY = top + titleHeight + pad;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
@@ -8968,6 +9198,48 @@ namespace jdi
|
||||
const bool checked = meanFilter ? lane.MeanFilterEnabled(ref)
|
||||
: (weakPeak ? lane.WeakPeakEnabled(ref)
|
||||
: lane.EdgeCenterEnabled(ref));
|
||||
if (weakPeak)
|
||||
{
|
||||
// 每个被勾选的信号各有两行滑块:等级(0–200%)与迭代次数(1 … 上限)。
|
||||
if (checked)
|
||||
{
|
||||
const float levelRowTop = rowY + meanRowHeight;
|
||||
const float iterRowTop = levelRowTop + weakPeakSliderRow;
|
||||
const gd::RectF levelTrack(
|
||||
weakPeakTrackLeft,
|
||||
levelRowTop + (weakPeakSliderRow - weakPeakTrackHeight) * 0.5f,
|
||||
weakPeakTrackWidth, weakPeakTrackHeight);
|
||||
const gd::RectF iterTrack(
|
||||
weakPeakTrackLeft,
|
||||
iterRowTop + (weakPeakSliderRow - weakPeakTrackHeight) * 0.5f,
|
||||
weakPeakTrackWidth, weakPeakTrackHeight);
|
||||
layout.rowLevelTracks.push_back(levelTrack);
|
||||
layout.rowLevelValues.push_back(gd::RectF(
|
||||
levelTrack.X + levelTrack.Width + 6.0f * m_scale, levelRowTop,
|
||||
weakPeakValueWidth, weakPeakSliderRow));
|
||||
layout.rowIterTracks.push_back(iterTrack);
|
||||
layout.rowIterValues.push_back(gd::RectF(
|
||||
iterTrack.X + iterTrack.Width + 6.0f * m_scale, iterRowTop,
|
||||
weakPeakValueWidth, weakPeakSliderRow));
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.rowLevelTracks.push_back(gd::RectF());
|
||||
layout.rowLevelValues.push_back(gd::RectF());
|
||||
layout.rowIterTracks.push_back(gd::RectF());
|
||||
layout.rowIterValues.push_back(gd::RectF());
|
||||
}
|
||||
layout.rowPoints.push_back(gd::RectF());
|
||||
layout.rowIterations.push_back(gd::RectF());
|
||||
layout.rowStats.push_back(gd::RectF());
|
||||
|
||||
rowY += meanRowHeight;
|
||||
if (checked)
|
||||
{
|
||||
rowY += weakPeakSliderRow * 2.0f;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (checked && itemHasParams)
|
||||
{
|
||||
const float editTop = rowY + meanRowHeight;
|
||||
@@ -9007,6 +9279,18 @@ namespace jdi
|
||||
return layout;
|
||||
}
|
||||
|
||||
int PlotView::PeakRowIndexOf(const EdgePanelLayout& layout, const SignalRef& ref) const
|
||||
{
|
||||
for (std::size_t index = 0; index < layout.rowRefs.size(); ++index)
|
||||
{
|
||||
if (layout.rowRefs[index] == ref)
|
||||
{
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool PlotView::IsOnEdgeCursorLine(int x, int y, int& laneIndex) const
|
||||
{
|
||||
laneIndex = -1;
|
||||
@@ -9650,10 +9934,25 @@ namespace jdi
|
||||
const Lane::EdgeCenterItem* centerItem = lane.FindEdgeCenterItem(ref);
|
||||
mix(static_cast<std::uint64_t>(centerItem != nullptr ? centerItem->points + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(centerItem != nullptr ? centerItem->iterations + 2 : 0));
|
||||
// 弱峰增强的逐信号参数(增强等级 % + 迭代次数)直接显示在面板输入框里。
|
||||
// 弱峰增强的逐信号参数(增强等级 % + 迭代次数 + 该信号的滑块上限)直接显示在面板上:
|
||||
// 上限决定迭代次数滑块的圆钮位置,因此也必须参与缓存键。
|
||||
const Lane::WeakPeakItem* peakItem = lane.FindWeakPeakItem(ref);
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->levelPercent + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->iterations + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->maxIterations + 2 : 0));
|
||||
}
|
||||
// 弱峰增强的滑块行矩形(每信号两行)。
|
||||
for (const gd::RectF& rect : layout.rowLevelTracks)
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowIterTracks)
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
for (const gd::RectF& rect : layout.rowIterValues)
|
||||
{
|
||||
mixRect(rect);
|
||||
}
|
||||
|
||||
// 鼠标落在面板内时,✕ / 复选框 / 滑块 / 底部按钮的悬停态都依赖精确坐标。
|
||||
@@ -9958,28 +10257,101 @@ namespace jdi
|
||||
continue;
|
||||
}
|
||||
|
||||
// 第二排:两个参数输入框。
|
||||
// MEAN/CENTER = 「均值点数 X / 迭代次数 N」;PEAK = 「增强等级 % / 迭代次数 N」。
|
||||
const Lane::MeanFilterItem* item = meanFilter ? tabLane.FindMeanFilterItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref);
|
||||
const Lane::WeakPeakItem* peakItem =
|
||||
weakPeak ? tabLane.FindWeakPeakItem(ref) : nullptr;
|
||||
const int shownPoints =
|
||||
weakPeak ? (peakItem != nullptr ? peakItem->levelPercent : 150)
|
||||
: ((item != nullptr) ? item->points : 5);
|
||||
const int shownOrder = weakPeak
|
||||
? (peakItem != nullptr ? peakItem->iterations : 1)
|
||||
: ((item != nullptr) ? item->iterations : 1);
|
||||
// MEAN / CENTER 的平滑参数条目(弱峰增强用自己的 `WeakPeakItem`)。
|
||||
const Lane::MeanFilterItem* item =
|
||||
(meanFilter || centerAxis)
|
||||
? (meanFilter ? tabLane.FindMeanFilterItem(ref)
|
||||
: tabLane.FindEdgeCenterItem(ref))
|
||||
: nullptr;
|
||||
|
||||
// 「弱峰增强」:每个被勾选的信号各有两行**滑块**
|
||||
//(增强等级 0–200% / 迭代次数 1 … 该信号自定义上限)。
|
||||
// 迭代次数行的右侧数值可点击 → 整行变为输入框以修改滑块上限。
|
||||
if (weakPeak)
|
||||
{
|
||||
const Lane::WeakPeakItem* peakItem = tabLane.FindWeakPeakItem(ref);
|
||||
if (peakItem != nullptr && rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f)
|
||||
{
|
||||
// 两行滑块的「标签」始终绘制(编辑上限时也要能看出在改哪一项);
|
||||
// 「轨道 + 数值」在编辑上限时改由 `PaintEditField` 画成输入框。
|
||||
const auto paintSliderLabel = [&](const gd::RectF& track,
|
||||
const wchar_t* label)
|
||||
{
|
||||
const float labelColumn =
|
||||
WeakPeakLabelColumnWidth(m_fonts->uiSmall);
|
||||
const float labelLeft =
|
||||
track.X - 6.0f * m_scale - labelColumn;
|
||||
UiText(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, track.Y - 8.0f * m_scale,
|
||||
labelColumn, track.Height + 16.0f * m_scale),
|
||||
m_theme->textMuted, TextAlign::LEFT);
|
||||
};
|
||||
const auto paintSliderBody = [&](const gd::RectF& track, double ratio,
|
||||
const std::wstring& valueText,
|
||||
bool active)
|
||||
{
|
||||
UiDrawSlider(g, track, ratio, m_theme->accent, active, m_scale);
|
||||
UiText(g, valueText, m_fonts->monoSmall,
|
||||
gd::RectF(track.X + track.Width + 6.0f * m_scale,
|
||||
track.Y - 8.0f * m_scale,
|
||||
WeakPeakValueColumnWidth(m_fonts->monoSmall),
|
||||
track.Height + 16.0f * m_scale),
|
||||
m_theme->textStrong, TextAlign::LEFT);
|
||||
};
|
||||
|
||||
const gd::RectF& levelTrack = layout.rowLevelTracks[rowIndex];
|
||||
const gd::RectF& iterTrack = layout.rowIterTracks[rowIndex];
|
||||
paintSliderLabel(levelTrack, kWeakPeakLevelLabel);
|
||||
paintSliderLabel(iterTrack, kWeakPeakIterationsLabel);
|
||||
|
||||
const bool levelActive = (m_dragMode == PlotDragMode::PEAK_LEVEL) &&
|
||||
(m_peakDragLane == static_cast<int>(index)) &&
|
||||
(m_peakDragRef == ref);
|
||||
const double levelRatio =
|
||||
static_cast<double>(std::max(0, std::min(kWeakPeakPercentMax,
|
||||
peakItem->levelPercent))) /
|
||||
(100.0 * kWeakPeakLevelMax);
|
||||
paintSliderBody(levelTrack, levelRatio,
|
||||
std::to_wstring(peakItem->levelPercent) + L"%",
|
||||
levelActive);
|
||||
|
||||
// 迭代次数滑块:量程 = 1 … 该信号自己的上限。
|
||||
const int peakMax = std::max(kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit,
|
||||
peakItem->maxIterations));
|
||||
const int peakIter = std::max(
|
||||
kWeakPeakIterationsMin, std::min(peakMax, peakItem->iterations));
|
||||
const double iterRatio =
|
||||
(peakMax > kWeakPeakIterationsMin)
|
||||
? (static_cast<double>(peakIter - kWeakPeakIterationsMin) /
|
||||
static_cast<double>(peakMax - kWeakPeakIterationsMin))
|
||||
: 0.0;
|
||||
const bool iterActive = (m_dragMode == PlotDragMode::PEAK_ITERATIONS) &&
|
||||
(m_peakDragLane == static_cast<int>(index)) &&
|
||||
(m_peakDragRef == ref);
|
||||
const bool editingMax =
|
||||
(m_editTarget == PlotEditTarget::PEAK_ITERATIONS_MAX) &&
|
||||
(m_editLane == static_cast<int>(index)) && (m_editMeanRef == ref);
|
||||
if (!editingMax)
|
||||
{
|
||||
paintSliderBody(iterTrack, iterRatio, std::to_wstring(peakIter),
|
||||
iterActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MEAN / CENTER:第二排的两个参数输入框。
|
||||
if (!weakPeak)
|
||||
{
|
||||
const int shownPoints = (item != nullptr) ? item->points : 5;
|
||||
const int shownOrder = (item != nullptr) ? item->iterations : 1;
|
||||
const PlotEditTarget pointsTarget =
|
||||
weakPeak ? PlotEditTarget::PEAK_LEVEL_PERCENT
|
||||
: (meanFilter ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS);
|
||||
meanFilter ? PlotEditTarget::MEAN_FILTER_POINTS
|
||||
: PlotEditTarget::EDGE_CENTER_POINTS;
|
||||
const PlotEditTarget orderTarget =
|
||||
weakPeak ? PlotEditTarget::PEAK_ITERATIONS
|
||||
: (meanFilter ? PlotEditTarget::MEAN_FILTER_ITERATIONS
|
||||
: PlotEditTarget::EDGE_CENTER_ITERATIONS);
|
||||
const wchar_t* const pointsLabel =
|
||||
weakPeak ? kWeakPeakLevelLabel : L"均值点数 X";
|
||||
meanFilter ? PlotEditTarget::MEAN_FILTER_ITERATIONS
|
||||
: PlotEditTarget::EDGE_CENTER_ITERATIONS;
|
||||
const auto paintField = [&](const gd::RectF& rect, const wchar_t* label,
|
||||
int value, PlotEditTarget target)
|
||||
{
|
||||
@@ -10016,9 +10388,10 @@ namespace jdi
|
||||
m_theme->textStrong, TextAlign::CENTER);
|
||||
}
|
||||
};
|
||||
paintField(layout.rowPoints[rowIndex], pointsLabel, shownPoints, pointsTarget);
|
||||
paintField(layout.rowPoints[rowIndex], L"均值点数 X", shownPoints, pointsTarget);
|
||||
paintField(layout.rowIterations[rowIndex], kWeakPeakIterationsLabel, shownOrder,
|
||||
orderTarget);
|
||||
}
|
||||
|
||||
// 第三排(中心轴):该信号在当前参数下的上升沿/下降沿统计结果。
|
||||
if (centerAxis && rowIndex < layout.rowStats.size() &&
|
||||
@@ -10143,6 +10516,23 @@ namespace jdi
|
||||
|
||||
// 编辑态:强调色外框 + 选区高亮 + 插入符,首次键入即整体替换(与常规输入框一致)。
|
||||
UiDrawEditField(g, fieldRect, m_edit, m_fonts->monoSmall, m_scale);
|
||||
|
||||
// 「迭代次数 N 上限」:输入框**以空内容开始**,未输入任何内容时以灰色提示当前上限值
|
||||
//(用户明确要求:"在未输入任何内容时,输入框内提示当前上限值")。
|
||||
// 提示文本比插入符稍微右移几像素,避免插入符压在上面看不清数字。
|
||||
if (m_editTarget == PlotEditTarget::PEAK_ITERATIONS_MAX && m_edit.Text().empty())
|
||||
{
|
||||
const int currentMax = m_peakEditCurrentMax > 0 ? m_peakEditCurrentMax
|
||||
: kWeakPeakIterationsDefaultMax;
|
||||
const std::wstring hint = std::to_wstring(currentMax);
|
||||
const float hintLeft = fieldRect.X + 11.0f * m_scale;
|
||||
UiTextAtClipped(g, hint, m_fonts->monoSmall, hintLeft,
|
||||
gd::RectF(hintLeft, fieldRect.Y,
|
||||
std::max(0.0f, fieldRect.X + fieldRect.Width -
|
||||
hintLeft - 4.0f * m_scale),
|
||||
fieldRect.Height),
|
||||
m_theme->textMuted, fieldRect);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::PaintSignalInteraction(gd::Graphics& g)
|
||||
|
||||
Reference in new issue
Block a user