主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能

① 主题自适应(`Theme.cpp`):新增 `SrgbLuminance` / `ContrastRatio` / `EnsureContrast` /
   `MixToLuminance` / `ThemeLaneBackground`;四套主题的结构色(gridWeak 1.30 / gridStrong 1.65 /
   axisLine 2.10 / borderStrong 1.70)按底色自动校正,Solarised Light 逐位不变;网格叠底改用
   `ThemeLaneBackground()`(4 处),深色主题下网格层与绘图区底色可辨。
② 派生预览曲线(`HighContrastDerived`)重写为**连续**方案:方向沿信号所在侧远离底色,
   目标亮度 = max(与底色对比度 3.2, 信号亮度 ± 0.18),再按亮度平滑混色;调用点传入真实网格底色
   (旧实现默认写死 Solarised Light 底色 ⇒ 深色主题永远选到更暗的那一侧、几乎看不见)。
③ 信号样式区「线宽 / 透明度」两行右侧新增**复位按钮**(`UiPaintResetIcon` 成为唯一实现,
   菜单/弱峰面板/样式区共用同一套笔画);默认线宽统一到 `DefaultSignalLineWidth(finiteCount)`
   (单点信号取 10.0)。按钮右边界与内容列对齐,内容列过窄时整列不显示。
④ 「均值滤波」面板重做:信号色叠底标题栏 + 复选框 + 展开三角 + 「均值点数 / 迭代次数」两行滑块
   (点击数值可改滑块上限、右侧复位)+ 内容区滚动;新增 `MeanFilterItem::{maxPoints,maxIterations,
   collapsed}` 与 `Lane::meanFilterScroll`,缓存键同步补全。
⑤ 浮动面板命中像素缓存时只贴**面板圆角形状之内**的像素(`CreateRoundRectRgn` 裁剪)并按本帧重画投影
   ⇒ 中键移视界后面板周围不再出现一圈旧波形。
⑥ 显示布局边界:每个浮动面板**独占一个绘制层**(按 `m_panelOrder` 递增),绘制侧
   `UiSetTextLayer` 在同一循环内同步推进 ⇒ 下层面板的边框/文字不再画到上层面板上。
⑦ 自由标记:采样候选距离改为**二维欧氏距离**(单点信号不再在整幅宽度内吸住光标),
   纵向光标画在鼠标位置、圆点落在真实采样点(新增 `FreeMarkerProbe::pointX`);
   `MinorCellSparse` 只对胜出信号算一次;折线缓存容量改为 `max(64, 信号数×2)` 且查找改为
   单键哈希 ⇒ MF4 的 285 信号合并网格中 `ProbeFreeMarker` 平均 0.11ms。

验证:`.preflight` 探针重建(`panelcheck` 71/71、`markercheck` 13/13、`ringcheck` 6/6,合计 90 条,
LTO 目标文件上复跑同样全绿);`make release` / `make debug` 零警告;产物 815,104 B。
文档:`agent_mem.md` 第十六轮(含补丁脚本与编码/行尾踩坑)。
This commit is contained in:
DeepSeek-V4.1-Flash committed 2026-10-30 03:10:51 +08:00
1 parent 71195c4c48
commit bdd703b88b
24 files changed
+1815 -256

No files matched your search

+24 -6
View File
@@ -119,8 +119,10 @@ namespace jdi
}
boxes.Add(m_widthTrackRect, 3);
boxes.Add(m_widthValueRect, 3);
boxes.Add(m_widthResetRect, 3);
boxes.Add(m_opacityTrackRect, 3);
boxes.Add(m_opacityValueRect, 3);
boxes.Add(m_opacityResetRect, 3);
// ── 「目标」行(样式区顶部,展示当前选中信号) ────────────────────
boxes.Add(m_targetRowRect, 2);
@@ -972,19 +974,35 @@ namespace jdi
}
// ---- 线宽行与透明度行:几何完全一致,保证纵向宽度对齐(线宽在上、透明度在下)----
const float valueWidth = std::min(kSliderValueWidth * m_scale, contentWidth * 0.4f);
const float trackWidth = std::max(24.0f, contentWidth - valueWidth - kSliderValueGap * m_scale);
// 两行右端各有一个**复位按钮**(用户 2026-10-29 要求):
// 按钮贴齐内容列右缘,数值文本与轨道在它左侧依次收缩 ⇒ 不会超出内容列。
// 内容列太窄(放不下"轨道 + 数值 + 按钮")时整列不显示复位按钮,避免把滑块挤没。
const float resetSize = (contentWidth >= kSliderResetSize * 3.0f * m_scale)
? std::min(kSliderResetSize * m_scale, rowHeight)
: 0.0f;
const float resetGap = (resetSize > 0.0f) ? kSliderResetGap * m_scale : 0.0f;
const float sliderWidth = contentWidth - (resetSize > 0.0f ? (resetSize + resetGap) : 0.0f);
const float valueWidth = std::min(kSliderValueWidth * m_scale, sliderWidth * 0.4f);
const float trackWidth = std::max(24.0f, sliderWidth - valueWidth - kSliderValueGap * m_scale);
const float trackHeight = kTrackHeight * m_scale;
const float valueLeft = contentLeft + trackWidth + kSliderValueGap * m_scale;
const float resetLeft = contentLeft + contentWidth - resetSize;
m_widthTrackRect =
gd::RectF(contentLeft, widthRow.Y + (rowHeight - trackHeight) * 0.5f, trackWidth, trackHeight);
m_widthValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, widthRow.Y,
valueWidth, rowHeight);
m_widthValueRect = gd::RectF(valueLeft, widthRow.Y, valueWidth, rowHeight);
m_widthResetRect =
(resetSize > 0.0f)
? gd::RectF(resetLeft, widthRow.Y + (rowHeight - resetSize) * 0.5f, resetSize, resetSize)
: gd::RectF();
m_opacityTrackRect = gd::RectF(contentLeft, opacityRow.Y + (rowHeight - trackHeight) * 0.5f,
trackWidth, trackHeight);
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, opacityRow.Y,
valueWidth, rowHeight);
m_opacityValueRect = gd::RectF(valueLeft, opacityRow.Y, valueWidth, rowHeight);
m_opacityResetRect =
(resetSize > 0.0f)
? gd::RectF(resetLeft, opacityRow.Y + (rowHeight - resetSize) * 0.5f, resetSize, resetSize)
: gd::RectF();
// 「目标」行的子几何依赖目标信号的名称与可见性(可能在未重排时变化),
// 因此每次重排后让缓存失效,下一次绘制 / 命中测试时按需重算。
+50 -1
View File
@@ -65,7 +65,9 @@ namespace jdi
if (UiPointInRect(m_adjustRects[kAdjustGain], fx, fy) ||
UiPointInRect(m_adjustRects[kAdjustMirror], fx, fy) ||
UiPointInRect(m_adjustRects[kAdjustAbsolute], fx, fy) || UiPointInRect(m_widthValueRect, fx, fy) ||
UiPointInRect(m_opacityValueRect, fx, fy))
UiPointInRect(m_opacityValueRect, fx, fy) ||
(m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy)) ||
(m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy)))
{
return LoadCursorW(nullptr, IDC_HAND);
}
@@ -652,4 +654,51 @@ namespace jdi
return true;
}
bool SignalPanel::ResetStyleLineWidth()
{
if (m_dataSet == nullptr || m_dataSetWritable == nullptr)
{
return false;
}
SignalStyle* style = StyleOf(SelectedRef());
if (style == nullptr)
{
return false;
}
const SignalData* signal = m_dataSet->Resolve(SelectedRef());
if (signal == nullptr)
{
return false;
}
// 默认线与新建信号同源(`DefaultSignalLineWidth`):单数据点信号用最大线宽,
// 其余用 `kDefaultSignalLineWidth`(用户明确要求"单值信号和普通信号的默认线宽不一样")。
const float target = DefaultSignalLineWidth(signal->finiteCount);
if (std::fabs(style->lineWidth - target) < 0.001f)
{
return false;
}
style->lineWidth = target;
return true;
}
bool SignalPanel::ResetStyleOpacity()
{
if (m_dataSetWritable == nullptr)
{
return false;
}
SignalStyle* style = StyleOf(SelectedRef());
if (style == nullptr)
{
return false;
}
if (std::fabs(style->opacity - 1.0f) < 0.001f)
{
return false;
}
style->opacity = 1.0f;
return true;
}
} // namespace jdi
@@ -195,6 +195,17 @@ namespace jdi
{
hoverStyle = StyleHit::WIDTH_VALUE;
}
// 两行右侧的复位按钮(在数值框右边,命中区不重叠,但顺序放在数值之后更安全)。
if (hoverStyle == StyleHit::NONE && m_widthResetRect.Width > 0.0f &&
UiPointInRect(m_widthResetRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_RESET;
}
if (hoverStyle == StyleHit::NONE && m_opacityResetRect.Width > 0.0f &&
UiPointInRect(m_opacityResetRect, fx, fy))
{
hoverStyle = StyleHit::OPACITY_RESET;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_adjustRects[kAdjustGain], fx, fy))
{
hoverStyle = StyleHit::GAIN_ADJUST;
@@ -424,6 +435,20 @@ namespace jdi
ScrollToSignalAnimated(SelectedRef());
return BeginEdit(StyleEditField::OPACITY);
}
// 两行右侧的**复位按钮**:把该行参数恢复为默认值(线宽按信号类型取默认、
// 不透明度回 100%)。与「系数/偏移」还原图标同一交互语义:就地回显、不弹任何东西。
if (m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy))
{
ResetStyleLineWidth();
ScrollToSignalAnimated(SelectedRef());
return true;
}
if (m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy))
{
ResetStyleOpacity();
ScrollToSignalAnimated(SelectedRef());
return true;
}
// 「调整」行:系数与镜像(按下只登记,**抬起**时才展开各自的二级面板,
// 与自定义颜色入口同一套交互)、绝对值(单击即切换)。
if (UiPointInRect(m_adjustRects[kAdjustGain], fx, fy))
@@ -821,6 +846,12 @@ namespace jdi
{
return false;
}
// 线宽 / 透明度两行右侧的复位按钮是"单次点击即生效"的按钮:双击不得被解释成别的语义。
if ((m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy)) ||
(m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy)))
{
return false;
}
UpdateTargetRowGeometry();
if (m_targetNameRect.Width > 0.0f && UiPointInRect(m_targetNameRect, fx, fy))
{
@@ -768,6 +768,9 @@ namespace jdi
m_theme->textMuted, TextAlign::CENTER);
return;
}
// 目标信号本身:线宽复位按钮需要它的有效采样点数(单点信号默认用最大线宽)。
const SignalData* signal =
(m_dataSet != nullptr) ? m_dataSet->Resolve(SelectedRef()) : nullptr;
const float labelWidth = kStyleLabelWidth * m_scale;
@@ -779,6 +782,27 @@ namespace jdi
m_theme->textMuted, TextAlign::LEFT);
};
// 「线宽 / 透明度」两行右侧的**复位按钮**(与「系数/偏移」二级菜单标题栏的还原图标同款)。
// `changed` = 当前值是否已偏离默认值:偏离时才用强调色(一眼看出"这里可以点"),
// 已是默认值时用弱色(仍可点,但结果不变)。
auto paintStyleReset = [&](const gd::RectF& rect, StyleHit hit, bool changed)
{
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
{
return;
}
const bool hot = (m_hoverStyle == hit);
if (hot)
{
UiFillRoundRect(g, gd::RectF(rect.X - 1.0f * m_scale, rect.Y - 1.0f * m_scale,
rect.Width + 2.0f * m_scale, rect.Height + 2.0f * m_scale),
3.0f * m_scale, WithAlpha(m_theme->accent, 0x33));
}
UiPaintResetIcon(g, rect, hot ? m_theme->accent
: (changed ? m_theme->text : m_theme->textMuted),
m_scale);
};
// ---- 第一行:颜色(预设色块 + 自定义颜色入口)----
{
paintRowLabel(0, L"颜色");
@@ -888,7 +912,7 @@ namespace jdi
paintToggle(kAdjustAbsolute, L"绝对值", style->absolute, StyleHit::ABSOLUTE_TOGGLE);
}
// ---- 第四行:线宽(数值可点击输入)----
// ---- 第四行:线宽(数值可点击输入;右侧为复位按钮)----
{
paintRowLabel(3, L"线宽");
@@ -913,6 +937,9 @@ namespace jdi
: fullText;
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong, TextAlign::RIGHT);
}
paintStyleReset(m_widthResetRect, StyleHit::WIDTH_RESET,
style->lineWidth !=
DefaultSignalLineWidth(signal != nullptr ? signal->finiteCount : 0));
}
// ---- 第五行:透明度(数值可点击输入)----
@@ -933,6 +960,8 @@ namespace jdi
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
m_theme->textStrong, TextAlign::RIGHT);
}
paintStyleReset(m_opacityResetRect, StyleHit::OPACITY_RESET,
std::fabs(style->opacity - 1.0f) > 0.001f);
}
}