均值面板越界根因 + 网格线进入主题配色 + 条目栏悬停/整行点击 + 均值点数下限 1
1. 「元素超出浮动面板」真根因(用户 2026-10-30 报告的截图)
- 绘制侧 PaintEdgeCursor 的 itemized 仍是 (centerAxis || meanFilter || weakPeak),
而布局侧 EdgePanelLayoutFor 早已是 (centerAxis || weakPeak)。
于是均值滤波面板被旧的「逐条目」分支**再画一遍**(每行名称 + 复选框,且没有任何裁切),
滚动后那些行的坐标是负值/越界 ⇒ 直接画到标题栏上。
- 修法:绘制侧与布局侧统一;旧分支的兜底循环跳过条件补 meanFilter。
底部「构建为新信号」由共享的 if (hasButton) 绘制,不受影响。
- 顺带删除「只画完全落在内容区里的行」的 fullyInside 门控:真因修掉后
g.SetClip(layout.content) 已足够,半截行正常显示,滚动观感连续。
2. 条目栏交互(与「自由标记」分组栏一致)
- 整行可点:复选框区 = 切换是否预览;已启用 = 展开/收起参数行;未启用 = 直接开始预览。
命中只要求点落在 layout.content 内(不再要求整行在内容区内),因此半截行也能操作。
- 悬停行 → 对应预览曲线高亮:新增 m_meanRowHoverLane / m_meanRowHoverRef,
由 OnMouseMove 维护、OnMouseLeave 与 InvalidateDerivedCaches 清除;
PaintDerivedCurve/PaintMeanFilterCurve/PaintWeakPeakCurve 新增 bool highlight,
先画 +5px 半透明同色光晕再把线加粗,行头叠一层 accent;悬停态变化会 InvalidateSceneCache。
3. 均值点数下限 1(用户要求:不可以为 0)
- kMeanFilterPointsMin 0 → 1;输入提交用 centerTarget ? 0 : kMeanFilterPointsMin
(中心轴边沿计数沿用旧语义 0 = 不平滑);预览与构建请求都再夹一次下限,
即使条目被外部写成 0,渲染/构建结果与 1 点逐位一致。
4. 网格线颜色成为主题配色的一部分
- struct Theme 新增 laneBg(网格叠底色);NormalizePlotContrast → FinalizeTheme,
先解 laneBg = EnsureContrast(plotBg, cardBg, 1.03),再让 gridWeak/gridStrong/
axisLine/borderStrong 统一对 laneBg 归一(1.30 / 1.70 / 2.10 / 1.70)。
- 四个主题各自给出显式取值:Dark 的网格线对叠底色原本只有 1.071(几乎不可见)⇒ 现 ≥1.30;
其它三个主题同步微调,网格可见性一致。ThemeLaneBackground 返回 laneBg。
5. 行三角尺寸统一
- 新增 kEdgePanelRowTriangleSize = 12 逻辑像素,均值滤波行与自由标记分组栏共用;
标题栏的 kEdgePanelDisclosureWidth = 20 保持不变。
6. 均值面板滚动条与文本
- 滚动条必须在 g.Restore(meanSave) 之后绘制(它在内容裁剪区之外,裁剪内画会被整条裁掉)。
- 滑块标签/数值改用 UiTextClipped(..., layout.content)(GDI 文本不受 GDI+ 裁剪约束)。
验证(全部离屏)
uicheck 69/69、lbcheck 21/21、lbresid 5/5、lbzorder 4/4、lbcovr 7/7、lbpanel 10/10、
derivecheck 16/16、round15check 28/28、themecheck 17/17、.preflight/meancheck 17/17;
.preflight/meanpix:滚到顶/中/底时标题栏与面板外均为 0 像素差异,内容区正常滚动;
rendercheck:A–H 八个场景哈希与基线完全一致;
make release 零 warning(805,376 B);make debug 零 warning。
同步文档:Documents/agent_mem.md 追加「2026-10-30 第十七轮」,
并把第十六轮里「均值点数(0…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
This commit is contained in:
1 parent
bdd703b88b
commit
2d83b494a3
9 files changed
+396
-100
No files matched your search
@@ -2241,9 +2241,12 @@ namespace jdi
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] curve 曲线数值数组(缺失处为 NaN)。
|
||||
* @param [IN] curveBegin `curve[0]` 对应的采样下标。
|
||||
* @param [IN] highlight 是否高亮(鼠标悬停在对应的面板条目上时):
|
||||
* 先画一圈半透明的"光晕"再把曲线本身画得更粗更亮,让用户一眼看出这条预览曲线对应哪个条目。
|
||||
*/
|
||||
void PaintDerivedCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
const std::vector<float>& curve, std::size_t curveBegin) const;
|
||||
const std::vector<float>& curve, std::size_t curveBegin,
|
||||
bool highlight = false) const;
|
||||
|
||||
/**
|
||||
* @brief 取得某个采样区间内的派生曲线(带缓存)。
|
||||
@@ -2465,9 +2468,10 @@ namespace jdi
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] points 均值点数 X(滑平半径)。
|
||||
* @param [IN] iterations 迭代次数 N(至少 1)。
|
||||
* @param [IN] highlight 是否高亮(鼠标悬停在该网格均值滤波面板的对应条目上)。
|
||||
*/
|
||||
void PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int points, int iterations) const;
|
||||
int points, int iterations, bool highlight = false) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的弱峰调整曲线(与均值滤波曲线同一配色派生规则)。
|
||||
@@ -2476,9 +2480,10 @@ namespace jdi
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] amountPercent 弱峰调整幅度(-400–400)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
* @param [IN] highlight 是否高亮(鼠标悬停在该网格弱峰调整面板的对应条目上)。
|
||||
*/
|
||||
void PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int amountPercent, int iterations) const;
|
||||
int amountPercent, int iterations, bool highlight = false) const;
|
||||
|
||||
/**
|
||||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||||
@@ -2862,6 +2867,18 @@ namespace jdi
|
||||
/** 「弱峰调整」每行滑块右侧的复位控件是否处于悬停态(用于“进入/离开”时只提示一次)。 */
|
||||
bool m_peakResetHover = false;
|
||||
|
||||
/**
|
||||
* @brief 上一帧鼠标悬停在「均值滤波 / 弱峰调整」面板的哪个**条目栏**上。
|
||||
*
|
||||
* 用途(用户 2026-10-30 要求):悬停某条信号的条目栏时,把该信号的**预览曲线**
|
||||
* 高亮出来(画一圈光晕 + 加粗),这样"列表里这一行 ↔ 网格里那条曲线"一眼对应。
|
||||
*
|
||||
* 与 `m_freeMarkerRowHover*` 同一套路:只在悬停状态**变化**时返回 `changed`,
|
||||
* 避免每帧都请求重绘。
|
||||
*/
|
||||
int m_meanRowHoverLane = -1;
|
||||
SignalRef m_meanRowHoverRef;
|
||||
|
||||
// ---------------------------------------------------------------- 自由标记
|
||||
|
||||
/** 最近一次解析出的「自由标记」跟随数据点(绘制与命中共用)。 */
|
||||
|
||||
@@ -384,6 +384,15 @@ namespace jdi
|
||||
*/
|
||||
inline const float kEdgePanelDisclosureWidth = 20.0f;
|
||||
|
||||
/**
|
||||
* @brief 面板内**条目行 / 分组栏**左侧展开三角的边长(逻辑像素)。
|
||||
*
|
||||
* ⚠️ 与标题栏的 `kEdgePanelDisclosureWidth`(20)**刻意不同**:行高只有 22 逻辑像素,
|
||||
* 20 的三角会把整行顶满、显得又大又笨(用户 2026-10-30 报告)。
|
||||
* 「均值滤波」的条目栏与「自由标记」的分组栏共用本值(12),观感完全一致。
|
||||
*/
|
||||
inline const float kEdgePanelRowTriangleSize = 12.0f;
|
||||
|
||||
// ⚠️ 这里曾有 `kFreeMarkerHoverTolerance = 8`(旧的非光标模式“纵向贴近”容差)。
|
||||
// 用户 2026-10-22 要求把自由标记的触发范围与「常规状态下触发信号曲线高亮」**统一**,
|
||||
// 因此现在直接复用 `kSignalHitTolerance`(6 逻辑像素)并改判“到折线的垂直距离”
|
||||
@@ -559,8 +568,14 @@ namespace jdi
|
||||
*/
|
||||
inline const wchar_t* const kMeanFilterPointsSliderLabel = L"均值点数";
|
||||
inline const wchar_t* const kMeanFilterIterationsSliderLabel = L"迭代次数";
|
||||
/** 「均值点数」下限(窗口 = 2X+1 ⇒ X=0 就是原信号)。 */
|
||||
inline const int kMeanFilterPointsMin = 0;
|
||||
/**
|
||||
* @brief 「均值点数」下限。
|
||||
*
|
||||
* ⚠️ **必须是 1**(用户 2026-10-30 要求):窗口 = `2X+1`,`X = 0` 等于"不做任何平滑",
|
||||
* 滑块拖到最左端时预览曲线与原始波形完全重合 —— 那不像"滤波",用户会以为功能坏了。
|
||||
* 下限取 1 ⇒ 最左端是 3 点均值,始终有可见的平滑效果。
|
||||
*/
|
||||
inline const int kMeanFilterPointsMin = 1;
|
||||
/** 「均值点数」滑块的默认上限(用户可点击数值改成其它正整数)。 */
|
||||
inline const int kMeanFilterPointsDefaultMax = 50;
|
||||
/** 「均值点数」上限允许的最大值(防止输入一个天文数字把滤波窗口撑爆)。 */
|
||||
|
||||
@@ -644,6 +644,9 @@ namespace jdi
|
||||
m_hoverLegend = SignalRef();
|
||||
m_dragSignal = SignalRef();
|
||||
m_dragTargetLane = -1;
|
||||
// 面板条目栏的悬停同样按引用保存(均值滤波 / 弱峰调整的预览曲线高亮)。
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
InvalidateSceneCache();
|
||||
}
|
||||
|
||||
|
||||
@@ -699,8 +699,11 @@ namespace jdi
|
||||
}
|
||||
const int maximum = isPoints ? 1000000 : 1000;
|
||||
int value = static_cast<int>(std::lround(parsed));
|
||||
// 迭代次数至少为 1(1 次迭代等于不做滤波);均值点数可以为 0。
|
||||
value = isPoints ? std::max(0, std::min(value, maximum))
|
||||
// 迭代次数至少为 1(1 次迭代等于不做滤波)。
|
||||
// 「均值滤波」的**均值点数下限为 1**(用户 2026-10-30 明确要求:不能为 0);
|
||||
// 「中心轴边沿计数」沿用旧语义(0 = 不做平滑),因此两者下限分开取。
|
||||
const int pointsMin = centerTarget ? 0 : kMeanFilterPointsMin;
|
||||
value = isPoints ? std::max(pointsMin, std::min(value, maximum))
|
||||
: std::max(1, std::min(value, maximum));
|
||||
|
||||
Lane::MeanFilterItem& item = centerTarget ? lane.EnsureEdgeCenterItem(m_editMeanRef)
|
||||
@@ -1824,6 +1827,49 @@ namespace jdi
|
||||
hoverPanelKind);
|
||||
}
|
||||
|
||||
// 「均值滤波 / 弱峰调整」面板**条目栏**的悬停 → 高亮该信号的预览曲线
|
||||
//(用户 2026-10-30 要求:"鼠标处于条目栏上方时,对应的预览曲线需要高亮提示")。
|
||||
{
|
||||
int hoverLane = -1;
|
||||
SignalRef hoverRef;
|
||||
if (overEdgePanel && m_tab != nullptr && panelLane >= 0 &&
|
||||
(static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::MEAN ||
|
||||
static_cast<EdgePanelKind>(hoverPanelKind) == EdgePanelKind::PEAK))
|
||||
{
|
||||
const EdgePanelLayout layout =
|
||||
EdgePanelLayoutFor(panelLane, static_cast<EdgePanelKind>(hoverPanelKind));
|
||||
if (UiPointInRect(layout.content, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex)
|
||||
{
|
||||
if (rowIndex >= layout.rowHeaders.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
const gd::RectF& headerRect = layout.rowHeaders[rowIndex];
|
||||
// 半截滚出的行**也算命中**:绘制时行本来就会被裁剪成半截显示,
|
||||
// 若这里要求“完整落在内容区内”,就会出现“看得见却悬停不到”的不一致。
|
||||
// 命中点本身已被限定在内容区里,因此不会出现“看得见一半却点到”的幽灵热区。
|
||||
if (UiPointInRect(headerRect, static_cast<float>(x), static_cast<float>(y)))
|
||||
{
|
||||
hoverLane = panelLane;
|
||||
hoverRef = layout.rowRefs[rowIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoverLane != m_meanRowHoverLane || !(hoverRef == m_meanRowHoverRef))
|
||||
{
|
||||
m_meanRowHoverLane = hoverLane;
|
||||
m_meanRowHoverRef = hoverRef;
|
||||
// 预览曲线在部分绘制路径里属于"静态场景"(整幅重绘会缓存它),
|
||||
// 因此悬停状态一变就作废场景缓存 —— 只在"进出某一行"时发生,不是每帧。
|
||||
InvalidateSceneCache();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 「弱峰调整」每行滑块右侧的**复位控件**:悬停时给出一次性提示(进入时设置、离开时清除)。
|
||||
// 与「系数/偏移」二级面板标题栏的还原图标同一交互语义(悬停高亮 + 手型 + 提示)。
|
||||
bool overPeakReset = false;
|
||||
@@ -2216,18 +2262,8 @@ namespace jdi
|
||||
{
|
||||
break;
|
||||
}
|
||||
// 只允许命中**完全落在内容区里**的行(部分滚出的行其矩形会与内容区
|
||||
// 边界重叠,放行会造出"看得见一半却能点"的幽灵热区)。
|
||||
if (rowIndex < layout.rowHeaders.size())
|
||||
{
|
||||
const gd::RectF& headerRect = layout.rowHeaders[rowIndex];
|
||||
if (headerRect.Y < layout.content.Y - 0.5f ||
|
||||
headerRect.Y + headerRect.Height >
|
||||
layout.content.Y + layout.content.Height + 0.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 行可以“半截”显示(绘制时会按内容区裁剪),因此这里不再要求整行落在内容区;
|
||||
// 命中点已由上面的 `insideContent` 限定在内容区内,不会抢走底部按钮的点击。
|
||||
const bool hasParams =
|
||||
rowIndex < layout.rowLevelTracks.size() &&
|
||||
layout.rowLevelTracks[rowIndex].Width > 0.0f &&
|
||||
@@ -2334,13 +2370,38 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 组头左侧三角:切换参数行的收起 / 展开(只有勾选后才有三角)。
|
||||
if (rowIndex < layout.rowTriangles.size() &&
|
||||
layout.rowTriangles[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowTriangles[rowIndex], fx, fy))
|
||||
// 条目栏(信号色叠底那一整行):**整行都能点**(用户 2026-10-30 要求,
|
||||
// 与「自由标记」分组栏一致:不再只能点小小的展开三角)。
|
||||
// 语义:
|
||||
// * 点在右端复选框上 → 切换"是否启用该信号的预览"(与复选框同一语义,
|
||||
// 因为整行已被本函数消费,复选框自己收不到点击了);
|
||||
// * 已在预览中 → 切换参数行的收起 / 展开;
|
||||
// * 尚未启用 → 直接启用(用户点这一行显然是想看它的预览)。
|
||||
if (rowIndex < layout.rowHeaders.size() &&
|
||||
layout.rowHeaders[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowHeaders[rowIndex], fx, fy))
|
||||
{
|
||||
Lane::MeanFilterItem& item = meanLane.EnsureMeanFilterItem(ref);
|
||||
item.collapsed = !item.collapsed;
|
||||
const bool onCheckBox =
|
||||
rowIndex < layout.rowChecks.size() &&
|
||||
layout.rowChecks[rowIndex].Width > 0.0f &&
|
||||
UiPointInRect(layout.rowChecks[rowIndex], fx, fy);
|
||||
const Lane::MeanFilterItem* existing = meanLane.FindMeanFilterItem(ref);
|
||||
if (onCheckBox)
|
||||
{
|
||||
ToggleLaneMeanFilterSignal(index, ref);
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
}
|
||||
else if (existing == nullptr)
|
||||
{
|
||||
// 未启用:点击整行即开始预览(用户点这一行显然是想看它的预览)。
|
||||
meanLane.EnsureMeanFilterItem(ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已启用:整行切换参数行的收起 / 展开。
|
||||
meanLane.EnsureMeanFilterItem(ref).collapsed = !existing->collapsed;
|
||||
}
|
||||
m_dragMode = PlotDragMode::NONE;
|
||||
return true;
|
||||
}
|
||||
@@ -2564,8 +2625,9 @@ namespace jdi
|
||||
MeanFilterBuild build;
|
||||
build.groupIndex = ref.groupIndex;
|
||||
build.signalIndex = ref.signalIndex;
|
||||
build.points = item->points;
|
||||
build.iterations = item->iterations;
|
||||
// 与预览一致:点数下限 1、迭代次数下限 1(用户 2026-10-30 要求)。
|
||||
build.points = std::max(kMeanFilterPointsMin, item->points);
|
||||
build.iterations = std::max(kMeanFilterIterationsMin, item->iterations);
|
||||
m_pendingMeanFilterItems.push_back(build);
|
||||
}
|
||||
if (!m_pendingMeanFilterItems.empty())
|
||||
@@ -3398,6 +3460,13 @@ namespace jdi
|
||||
m_freeMarkerRowHoverItem = static_cast<std::size_t>(-1);
|
||||
changed = true;
|
||||
}
|
||||
// 「均值滤波 / 弱峰调整」条目栏的悬停高亮同样随鼠标离开清除(否则预览曲线一直亮着)。
|
||||
if (m_meanRowHoverLane != -1 || m_meanRowHoverRef.IsValid())
|
||||
{
|
||||
m_meanRowHoverLane = -1;
|
||||
m_meanRowHoverRef = SignalRef();
|
||||
changed = true;
|
||||
}
|
||||
if (m_cursorHover)
|
||||
{
|
||||
m_cursorHover = false;
|
||||
|
||||
@@ -2063,6 +2063,9 @@ namespace jdi
|
||||
}
|
||||
const int saveState = g.Save();
|
||||
g.SetClip(lane.rect, gd::CombineModeIntersect);
|
||||
// 面板条目栏的悬停高亮(用户 2026-10-30 要求):鼠标停在哪一行,就把那条**预览曲线**
|
||||
// 画一圈光晕 + 加粗。`m_meanRowHoverLane/Ref` 由 `OnMouseMove` 维护。
|
||||
const bool hoverActive = (m_meanRowHoverLane == laneIndex) && m_meanRowHoverRef.IsValid();
|
||||
for (const SignalRef& ref : tabLane.signals)
|
||||
{
|
||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
||||
@@ -2070,6 +2073,7 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool hovered = hoverActive && (ref == m_meanRowHoverRef);
|
||||
if (centerOn)
|
||||
{
|
||||
const Lane::EdgeCenterItem* centerItem = tabLane.FindEdgeCenterItem(ref);
|
||||
@@ -2083,7 +2087,7 @@ namespace jdi
|
||||
const Lane::MeanFilterItem* item = tabLane.FindMeanFilterItem(ref);
|
||||
if (item != nullptr)
|
||||
{
|
||||
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations);
|
||||
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations, hovered);
|
||||
}
|
||||
}
|
||||
if (weakPeakOn)
|
||||
@@ -2097,7 +2101,7 @@ namespace jdi
|
||||
const int boostIterations = std::max(
|
||||
kWeakPeakIterationsMin,
|
||||
std::min(kWeakPeakIterationsMaxLimit, item->iterations));
|
||||
PaintWeakPeakCurve(g, resolved, lane, amountPercent, boostIterations);
|
||||
PaintWeakPeakCurve(g, resolved, lane, amountPercent, boostIterations, hovered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3158,7 +3162,7 @@ namespace jdi
|
||||
|
||||
void PlotView::PaintDerivedCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, const std::vector<float>& curve,
|
||||
std::size_t curveBegin) const
|
||||
std::size_t curveBegin, bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -3273,6 +3277,27 @@ namespace jdi
|
||||
pen.SetLineJoin(gd::LineJoinRound);
|
||||
pen.SetStartCap(gd::LineCapRound);
|
||||
pen.SetEndCap(gd::LineCapRound);
|
||||
// 悬停高亮(用户 2026-10-30 要求):先沿同一批顶点画一遍**加粗的半透明光晕**,
|
||||
// 再把曲线本身画粗一点。光晕垫在曲线下面 ⇒ 不遮蔽曲线本身、也不影响它的颜色。
|
||||
if (highlight)
|
||||
{
|
||||
gd::Pen halo(ToGdiColor(WithAlpha(derived, 0x55u)),
|
||||
std::max(1.0f, resolved.style->lineWidth) * m_scale + 5.0f * m_scale);
|
||||
halo.SetLineJoin(gd::LineJoinRound);
|
||||
halo.SetStartCap(gd::LineCapRound);
|
||||
halo.SetEndCap(gd::LineCapRound);
|
||||
for (std::size_t k = 0; k < breaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = breaks[k];
|
||||
const std::size_t stop = (k + 1 < breaks.size()) ? breaks[k + 1] : points.size();
|
||||
if (stop <= start + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
g.DrawLines(&halo, points.data() + start, static_cast<INT>(stop - start));
|
||||
}
|
||||
pen.SetWidth(std::max(1.0f, resolved.style->lineWidth) * m_scale + 1.6f * m_scale);
|
||||
}
|
||||
for (std::size_t k = 0; k < breaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = breaks[k];
|
||||
@@ -3287,7 +3312,8 @@ namespace jdi
|
||||
}
|
||||
|
||||
void PlotView::PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int points, int iterations) const
|
||||
const LaneVisual& lane, int points, int iterations,
|
||||
bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -3305,6 +3331,10 @@ namespace jdi
|
||||
// **逐位一致**(用户明确要求"预览显示效果要和构建为新信号一致")。
|
||||
// 整段窗口超过异步门槛时由后台线程计算,绘制线程本帧用已算好的旧曲线顶替,
|
||||
// 因此不会阻塞交互;小信号同步计算。
|
||||
// ⚠️ 「均值点数」的下限是 `kMeanFilterPointsMin`(=1,用户 2026-10-30 明确要求不能为 0):
|
||||
// 即使条目里的值被外部写成了 0/负数,预览也不能按“不平滑”画出来。
|
||||
points = std::max(kMeanFilterPointsMin, points);
|
||||
iterations = std::max(kMeanFilterIterationsMin, iterations);
|
||||
std::size_t spanBegin = 0;
|
||||
const std::vector<float>* curve = CurveSpanFor(DerivedCurveKind::MEAN_FILTER, *resolved.data,
|
||||
points, iterations, 0, 0, count, spanBegin, true);
|
||||
@@ -3312,11 +3342,12 @@ namespace jdi
|
||||
{
|
||||
return;
|
||||
}
|
||||
PaintDerivedCurve(g, resolved, lane, *curve, spanBegin);
|
||||
PaintDerivedCurve(g, resolved, lane, *curve, spanBegin, highlight);
|
||||
}
|
||||
|
||||
void PlotView::PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, int amountPercent, int iterations) const
|
||||
const LaneVisual& lane, int amountPercent, int iterations,
|
||||
bool highlight) const
|
||||
{
|
||||
if (resolved.data == nullptr || resolved.axis == nullptr || resolved.style == nullptr)
|
||||
{
|
||||
@@ -4368,8 +4399,12 @@ namespace jdi
|
||||
const bool meanFilter = (kind == EdgePanelKind::MEAN);
|
||||
const bool weakPeak = (kind == EdgePanelKind::PEAK);
|
||||
const bool freeMarker = (kind == EdgePanelKind::MARK);
|
||||
// 「均值滤波」与「弱峰调整」共用「逐条目 + 底部按钮」排布。
|
||||
const bool itemized = (centerAxis || meanFilter || weakPeak);
|
||||
// ⚠️ `itemized` 只表示**旧的逐条目排布**(中心轴 / 弱峰调整)。
|
||||
// 「均值滤波」自 2026-10-30 起有自己的**可滚动分支**(`if (meanFilter)` 内),
|
||||
// 若它仍被算作 `itemized`,旧分支会**再画一遍**每行的名称 + 复选框 ——
|
||||
// 且旧分支**没有任何裁切**,滚动时这些元素就会画到标题栏上
|
||||
//(用户 2026-10-30 报告“元素超出浮动面板”的根因)。
|
||||
const bool itemized = (centerAxis || weakPeak);
|
||||
const bool hasButton = (meanFilter || weakPeak);
|
||||
{
|
||||
if (index >= count)
|
||||
@@ -4878,10 +4913,17 @@ namespace jdi
|
||||
const Lane::MeanFilterItem* item = tabLane.FindMeanFilterItem(ref);
|
||||
const bool checked = (item != nullptr);
|
||||
|
||||
// 叠底:先铺一层不透明同色再叠 0xE8(与「自由标记」分组栏同一做法),
|
||||
// 这样滚动时下层内容不会透出来。
|
||||
// 内容区已由 `g.SetClip(layout.content)` 裁切(行可以“半截”显示,滚动更顺滑);
|
||||
// 已经完整滚出可视范围的行在上面就 `continue` 了,这里不再额外门控。
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale, signalColor);
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale, WithAlpha(signalColor, 0xE8));
|
||||
// 条目栏悬停:在信号色叠底上再压一层强调色,给出"这一行对应哪条曲线"的提示
|
||||
//(曲线本身的高亮见 `PaintLaneDerivedOverlays`)。
|
||||
if (m_meanRowHoverLane == static_cast<int>(index) && m_meanRowHoverRef == ref)
|
||||
{
|
||||
UiFillRoundRect(g, header, 3.0f * m_scale,
|
||||
WithAlpha(m_theme->accent, 0x2E));
|
||||
}
|
||||
|
||||
// 左侧三角(仅勾选时存在):朝下 = 展开、朝右 = 收起。
|
||||
const gd::RectF& triangle = layout.rowTriangles[rowIndex];
|
||||
@@ -4917,26 +4959,25 @@ namespace jdi
|
||||
const wchar_t* label, int current, int minimum,
|
||||
int maximum, bool active)
|
||||
{
|
||||
if (track.Width <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float labelLeft = track.X - 6.0f * m_scale -
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label);
|
||||
UiText(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, track.Y - 8.0f * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label),
|
||||
track.Height + 16.0f * m_scale),
|
||||
m_theme->textMuted, TextAlign::LEFT);
|
||||
// ⚠️ **必须用 `UiTextClipped`(而不是 `UiText`)**:GDI 文本不受
|
||||
// GDI+ `SetClip` 约束,滚动时标签/数值会画到内容区之外
|
||||
//(用户 2026-10-30 报告"元素超出了浮动面板"的直接原因)。
|
||||
UiTextClipped(g, label, m_fonts->uiSmall,
|
||||
gd::RectF(labelLeft, track.Y - 8.0f * m_scale,
|
||||
UiMeasureTextWidth(m_fonts->uiSmall, label),
|
||||
track.Height + 16.0f * m_scale),
|
||||
m_theme->textMuted, TextAlign::LEFT, layout.content);
|
||||
const double ratio =
|
||||
(maximum > minimum)
|
||||
? (static_cast<double>(current - minimum) /
|
||||
static_cast<double>(maximum - minimum))
|
||||
: 0.0;
|
||||
UiDrawSlider(g, track, ratio, m_theme->accent, active, m_scale);
|
||||
UiText(g, std::to_wstring(current), m_fonts->monoSmall,
|
||||
gd::RectF(valueRect.X, valueRect.Y, valueColumn, valueRect.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT);
|
||||
UiTextClipped(g, std::to_wstring(current), m_fonts->monoSmall,
|
||||
gd::RectF(valueRect.X, valueRect.Y, valueColumn, valueRect.Height),
|
||||
m_theme->textStrong, TextAlign::LEFT, layout.content);
|
||||
// 复位按钮:悬停高亮 + 与「系数/偏移」菜单同款图标。
|
||||
const bool hot =
|
||||
(m_dragMode != PlotDragMode::EDGE_PANEL_MOVE) &&
|
||||
@@ -4998,6 +5039,18 @@ namespace jdi
|
||||
}
|
||||
|
||||
g.Restore(meanSave);
|
||||
|
||||
// ---- 内容滚动条(只在内容超高时出现;底部按钮不参与滚动)----
|
||||
// ⚠️ 必须在 `g.Restore(meanSave)` **之后**画:滚动条位于内容区右侧、在
|
||||
// 内容裁剪区之外,若在裁剪内画会被整条裁掉(用户 2026-10-30 报告
|
||||
// "全部启用后没有出现滚动条"的根因)。
|
||||
if (layout.scrollTrack.Width > 0.0f)
|
||||
{
|
||||
UiFillRoundRect(g, layout.scrollTrack, layout.scrollTrack.Width * 0.5f,
|
||||
WithAlpha(m_theme->border, 0x88));
|
||||
UiFillRoundRect(g, layout.scrollThumb, layout.scrollThumb.Width * 0.5f,
|
||||
m_theme->borderStrong);
|
||||
}
|
||||
}
|
||||
// 「自由标记」面板:按信号分组的**分组栏**(信号色托底 + 三角 + 名称 +
|
||||
// 「N 标记点」+ ✕),下属固定点条目六列对齐(序号 / 取值 / 枚举 / 增量 / 时刻 / 间隔),
|
||||
@@ -5346,9 +5399,10 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (itemized || freeMarker)
|
||||
if (itemized || freeMarker || meanFilter)
|
||||
{
|
||||
// 逐条目绘制已在上面完成(自由标记同样是逐条目排布)。
|
||||
// 逐条目绘制已在上面完成(自由标记同样是逐条目排布,
|
||||
// 均值滤波走自己的可滚动分支)。
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t signalColor = StyleColor(*resolved.style);
|
||||
|
||||
@@ -630,9 +630,9 @@ namespace jdi
|
||||
layout.rowNameRects.push_back(gd::RectF(contentLeft, rowY, innerWidth, groupRow));
|
||||
layout.rowTriangles.push_back(
|
||||
checked ? gd::RectF(contentLeft + 3.0f * m_scale,
|
||||
rowY + (groupRow - kEdgePanelDisclosureWidth * m_scale) * 0.5f,
|
||||
kEdgePanelDisclosureWidth * m_scale,
|
||||
kEdgePanelDisclosureWidth * m_scale)
|
||||
rowY + (groupRow - kEdgePanelRowTriangleSize * m_scale) * 0.5f,
|
||||
kEdgePanelRowTriangleSize * m_scale,
|
||||
kEdgePanelRowTriangleSize * m_scale)
|
||||
: gd::RectF());
|
||||
layout.rowChecks.push_back(
|
||||
gd::RectF(contentLeft + innerWidth - closeSize - 4.0f * m_scale,
|
||||
@@ -987,8 +987,9 @@ namespace jdi
|
||||
group.pinned = (barTop > rowY + 0.5f);
|
||||
group.header = gd::RectF(layout.content.X, barTop, innerWidth, groupRow);
|
||||
group.triangle = gd::RectF(layout.content.X + 3.0f * m_scale,
|
||||
barTop + (groupRow - 12.0f * m_scale) * 0.5f,
|
||||
12.0f * m_scale, 12.0f * m_scale);
|
||||
barTop + (groupRow - kEdgePanelRowTriangleSize * m_scale) * 0.5f,
|
||||
kEdgePanelRowTriangleSize * m_scale,
|
||||
kEdgePanelRowTriangleSize * m_scale);
|
||||
group.close = gd::RectF(layout.content.X + innerWidth - closeSize - 2.0f * m_scale,
|
||||
barTop + (groupRow - closeSize) * 0.5f, closeSize, closeSize);
|
||||
float pinnedBottom = barTop + groupRow;
|
||||
|
||||
@@ -120,7 +120,23 @@ namespace jdi
|
||||
std::uint32_t MixToLuminance(std::uint32_t color, double targetLuminance);
|
||||
|
||||
/**
|
||||
* @brief 界面主题色板(Solarised Light 派生)。
|
||||
* @brief 界面主题色板。
|
||||
*
|
||||
* ## 结构色的对比度约定(重要,改配色前必读)
|
||||
*
|
||||
* `gridWeak` / `gridStrong` / `axisLine` / `borderStrong` 这些“结构色”是**主题配色的一部分**
|
||||
* (每个主题各自给值),它们画在**网格叠底**(`laneBg`)之上,而不是绘图区底色 `plotBg`。
|
||||
* 因此它们的最小对比度一律针对 `laneBg` 校验:
|
||||
*
|
||||
* | 字段 | 最小对比度(相对 `laneBg`) |
|
||||
* | --- | ---: |
|
||||
* | `gridWeak` | 1.30 |
|
||||
* | `gridStrong` | 1.70 |
|
||||
* | `axisLine` | 2.10 |
|
||||
* | `borderStrong` | 1.70 |
|
||||
*
|
||||
* 各主题给出的值本就满足上述下限,因此最终化时**逐位返回原色**、不做任何改色;
|
||||
* 这些阈值只是“不允许配置出看不见的网格线”的兜底。
|
||||
*/
|
||||
struct Theme
|
||||
{
|
||||
@@ -143,9 +159,19 @@ namespace jdi
|
||||
std::uint32_t pressed = 0; /**< 按下底色。 */
|
||||
std::uint32_t selection = 0; /**< 选中底色。 */
|
||||
|
||||
std::uint32_t gridWeak = 0; /**< 次级网格线颜色。 */
|
||||
std::uint32_t gridStrong = 0; /**< 主要网格线颜色。 */
|
||||
std::uint32_t gridWeak = 0; /**< 次级网格线颜色(主要刻度 + 五等分线)。 */
|
||||
std::uint32_t gridStrong = 0; /**< 主要网格线颜色(零线 / 加重线)。 */
|
||||
std::uint32_t axisLine = 0; /**< 坐标轴线颜色。 */
|
||||
/**
|
||||
* @brief 每条网格内部的**叠底**颜色(绘图区底色之外的“卡片层”)。
|
||||
*
|
||||
* ⚠️ 网格线是画在**它**上面(不是 `plotBg` 上),因此结构色的对比度必须针对
|
||||
* 本字段校验(见 `Theme.hpp` 顶部的 `EnsureContrast` 约定);落在深色主题上时
|
||||
* 两者相差很大,只盯着 `plotBg` 算会让网格线“几乎看不见”(用户 2026-10-30 报告)。
|
||||
*
|
||||
* 0 表示“未设置”:最终化时回退到 `cardBg` 并保证与 `plotBg` 有最小对比度。
|
||||
*/
|
||||
std::uint32_t laneBg = 0;
|
||||
std::uint32_t cursor = 0; /**< 时间光标颜色(单/双光标共用同一样式)。 */
|
||||
std::uint32_t readoutBg = 0; /**< 数值读数底色。 */
|
||||
std::uint32_t danger = 0; /**< 警示色。 */
|
||||
|
||||
@@ -36,31 +36,40 @@ namespace jdi
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 把绘图区内部的"结构性"颜色按**底色**自动校正到足够对比(见 `EnsureContrast`)。
|
||||
* @brief 主题**最终化**:确定网格叠底色,并保证结构色在**它**之上可辨。
|
||||
*
|
||||
* 阈值(对比度)取"Solarised Light 现有配色刚好达标"的水位:
|
||||
* 该主题的历史配色是人工调过的,达标时 `EnsureContrast` 逐位返回原色 ⇒ 默认主题
|
||||
* **一个像素都不会变**;其它主题(尤其深色)则自动把网格线/时间轴/分界线提亮。
|
||||
* ⚠️⚠️ **参照底色必须是 `laneBg`(网格叠底),不是 `plotBg`**:
|
||||
* 网格线画在每条网格内部的叠底上。深色主题里 `plotBg`(#1E1E1E)与 `laneBg`
|
||||
* (#2D2D30)亮度差得远,只按 `plotBg` 校正会让网格线落进叠底色的亮度范围
|
||||
* ⇒ 实测对比度只有 **1.07**(几乎看不见,用户 2026-10-30 报告)。
|
||||
*
|
||||
* | 字段 | 阈值 | Solarised Light 现状 |
|
||||
* 阈值取"Solarised Light 现有配色刚好达标"的水位(针对 `laneBg` 实测:
|
||||
* 1.335 / 1.771 / 2.395 / 2.395),因此默认主题**逐位不变**;其它主题若不足才被校正。
|
||||
*
|
||||
* | 字段 | 阈值(相对 `laneBg`) | Solarised Light 现状 |
|
||||
* | --- | ---: | ---: |
|
||||
* | `gridWeak`(网格主刻线 + 五等分线基色) | 1.30 | 约 1.38 |
|
||||
* | `gridStrong`(零线/加重线) | 1.65 | 约 1.80 |
|
||||
* | `axisLine`(时间轴、网格分界线、绘图区外框) | 2.10 | 约 2.43 |
|
||||
* | `borderStrong`(网格间分界线) | 1.70 | 约 2.43 |
|
||||
* | `gridWeak`(网格主刻线 + 五等分线基色) | 1.30 | 1.335 |
|
||||
* | `gridStrong`(零线/加重线) | 1.70 | 1.771 |
|
||||
* | `axisLine`(时间轴、网格分界线、绘图区外框) | 2.10 | 2.395 |
|
||||
* | `borderStrong`(网格间分界线) | 1.70 | 2.395 |
|
||||
*/
|
||||
Theme NormalizePlotContrast(Theme theme)
|
||||
Theme FinalizeTheme(Theme theme)
|
||||
{
|
||||
const std::uint32_t plot = theme.plotBg | 0xFF000000u;
|
||||
theme.gridWeak = EnsureContrast(plot, theme.gridWeak | 0xFF000000u, 1.30);
|
||||
theme.gridStrong = EnsureContrast(plot, theme.gridStrong | 0xFF000000u, 1.65);
|
||||
theme.axisLine = EnsureContrast(plot, theme.axisLine | 0xFF000000u, 2.10);
|
||||
theme.borderStrong = EnsureContrast(plot, theme.borderStrong | 0xFF000000u, 1.70);
|
||||
// 网格叠底:未显式配置时回退到 `cardBg`,并保证与绘图区底色可辨(1.03)。
|
||||
const std::uint32_t laneSource =
|
||||
(theme.laneBg != 0u) ? (theme.laneBg | 0xFF000000u) : (theme.cardBg | 0xFF000000u);
|
||||
theme.laneBg = EnsureContrast(plot, laneSource, 1.03);
|
||||
|
||||
theme.gridWeak = EnsureContrast(theme.laneBg, theme.gridWeak | 0xFF000000u, 1.30);
|
||||
theme.gridStrong = EnsureContrast(theme.laneBg, theme.gridStrong | 0xFF000000u, 1.70);
|
||||
theme.axisLine = EnsureContrast(theme.laneBg, theme.axisLine | 0xFF000000u, 2.10);
|
||||
theme.borderStrong = EnsureContrast(theme.laneBg, theme.borderStrong | 0xFF000000u, 1.70);
|
||||
return theme;
|
||||
}
|
||||
|
||||
/** @brief 全局唯一主题实例(Solarised Light;历史默认主题)。 */
|
||||
const Theme kSolarizedLight = NormalizePlotContrast([]
|
||||
const Theme kSolarizedLight = FinalizeTheme([]
|
||||
{
|
||||
Theme theme;
|
||||
theme.windowBg = MakeArgb(kBase3);
|
||||
@@ -82,8 +91,10 @@ namespace jdi
|
||||
theme.pressed = MakeArgb(0xD8CFB6u);
|
||||
theme.selection = MakeArgb(0xDCEAF6u);
|
||||
|
||||
// 网格线需要在绘图区底色(kBase3)上清晰可辨,同时又要与网格边界
|
||||
// (axisLine/borderStrong 的蓝灰)明显区分——因此采用偏暖的加深米色。
|
||||
// 网格线需要在**网格叠底**(kBase3 推暗 1.03 ≈ #F9F2E0)上清晰可辨,同时又要与
|
||||
// 网格边界(axisLine/borderStrong 的蓝灰)明显区分——因此采用偏暖的加深米色。
|
||||
// 这三个值相对叠底色的对比度分别是 1.335 / 1.771 / 2.395,已达标 ⇒ 最终化不改动。
|
||||
theme.laneBg = MakeArgb(0xF9F2E0u);
|
||||
theme.gridWeak = MakeArgb(0xDDD3B8u);
|
||||
theme.gridStrong = MakeArgb(0xC3B896u);
|
||||
theme.axisLine = MakeArgb(kBase1);
|
||||
@@ -100,7 +111,7 @@ namespace jdi
|
||||
* 与 Solarised Light 的区别:底色是**中性白/浅灰**而不是暖米色,
|
||||
* 强调色是 VS Code 的系统蓝 `#0078D4`,描边是中性灰。
|
||||
*/
|
||||
const Theme kLight = NormalizePlotContrast([]
|
||||
const Theme kLight = FinalizeTheme([]
|
||||
{
|
||||
Theme theme;
|
||||
theme.windowBg = MakeArgb(0xFFFFFFu); // editor.background
|
||||
@@ -109,7 +120,7 @@ namespace jdi
|
||||
theme.cardBg = MakeArgb(0xFFFFFFu);
|
||||
theme.plotBg = MakeArgb(0xFFFFFFu);
|
||||
theme.border = MakeArgb(0xE5E5E5u); // panel.border
|
||||
theme.borderStrong = MakeArgb(0xCECECEu);
|
||||
theme.borderStrong = MakeArgb(0xC3C3C3u);
|
||||
|
||||
theme.text = MakeArgb(0x3B3B3Bu); // foreground
|
||||
theme.textStrong = MakeArgb(0x1F1F1Fu);
|
||||
@@ -122,11 +133,12 @@ namespace jdi
|
||||
theme.pressed = MakeArgb(0xE8E8E8u);
|
||||
theme.selection = MakeArgb(0xE8E8E8u);
|
||||
|
||||
// 白底上网格线必须够淡(否则抢波形),但主刻线要能与次级线区分。
|
||||
// ⚠️ 这三个颜色会被 `NormalizePlotContrast` 按白底自动校正到可见水位。
|
||||
theme.gridWeak = MakeArgb(0xEDEDEDu);
|
||||
theme.gridStrong = MakeArgb(0xD6D6D6u);
|
||||
theme.axisLine = MakeArgb(0xCECECEu);
|
||||
// 网格叠底比绘图区白底略暗一档,便于分辨相邻网格;
|
||||
// 结构色按**叠底色**选中(对比度:1.30 / 1.70 / 2.10),不要再用原白底的值。
|
||||
theme.laneBg = MakeArgb(0xFBFBFBu);
|
||||
theme.gridWeak = MakeArgb(0xDEDEDEu);
|
||||
theme.gridStrong = MakeArgb(0xC3C3C3u);
|
||||
theme.axisLine = MakeArgb(0xAFAFAFu);
|
||||
theme.cursor = MakeArgb(0xD18616u); // VS Code charts.orange
|
||||
theme.readoutBg = MakeArgb(0xF3F3F3u);
|
||||
theme.danger = MakeArgb(0xE51400u); // errorForeground
|
||||
@@ -137,7 +149,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 深色主题(参考 VS Code Dark+)。
|
||||
*/
|
||||
const Theme kDark = NormalizePlotContrast([]
|
||||
const Theme kDark = FinalizeTheme([]
|
||||
{
|
||||
Theme theme;
|
||||
theme.windowBg = MakeArgb(0x1E1E1Eu); // editor.background
|
||||
@@ -146,7 +158,7 @@ namespace jdi
|
||||
theme.cardBg = MakeArgb(0x2D2D30u);
|
||||
theme.plotBg = MakeArgb(0x1E1E1Eu);
|
||||
theme.border = MakeArgb(0x3C3C3Cu);
|
||||
theme.borderStrong = MakeArgb(0x4A4A4Au);
|
||||
theme.borderStrong = MakeArgb(0x505050u);
|
||||
|
||||
theme.text = MakeArgb(0xCCCCCCu);
|
||||
theme.textStrong = MakeArgb(0xFFFFFFu);
|
||||
@@ -159,11 +171,13 @@ namespace jdi
|
||||
theme.pressed = MakeArgb(0x37373Du);
|
||||
theme.selection = MakeArgb(0x04395Eu);
|
||||
|
||||
// ⚠️ 深色底上的原始取值几乎与底色同亮度(差 2~3 个色阶)⇒ 交给
|
||||
// `NormalizePlotContrast` 按 `plotBg` 自动提亮到可见水位。
|
||||
theme.gridWeak = MakeArgb(0x2B2B2Bu);
|
||||
theme.gridStrong = MakeArgb(0x3A3A3Au);
|
||||
theme.axisLine = MakeArgb(0x4A4A4Au);
|
||||
// ⚠️ 深色主题的结构色**必须比旧值亮得多**:它们画在网格叠底(#2D2D30)上,
|
||||
// 旧值(#2B2B2B / #3A3A3A)与叠底几乎同亮度(实测对比度 1.07)⇒ 网格线看不见。
|
||||
// 这里的值按叠底色选:1.30 / 1.70 / 2.10。
|
||||
theme.laneBg = MakeArgb(0x2D2D30u);
|
||||
theme.gridWeak = MakeArgb(0x3F3F3Fu);
|
||||
theme.gridStrong = MakeArgb(0x505050u);
|
||||
theme.axisLine = MakeArgb(0x5E5E5Eu);
|
||||
theme.cursor = MakeArgb(0xD18616u);
|
||||
theme.readoutBg = MakeArgb(0x2D2D30u);
|
||||
theme.danger = MakeArgb(0xF44747u);
|
||||
@@ -177,7 +191,7 @@ namespace jdi
|
||||
* 底色用 base03、面板用 base02,强调色仍是 Solarised 那一套 —— 因此波形配色表
|
||||
* (`SignalPalette`)与 Solarised Light 完全一致。
|
||||
*/
|
||||
const Theme kSolarizedDark = NormalizePlotContrast([]
|
||||
const Theme kSolarizedDark = FinalizeTheme([]
|
||||
{
|
||||
Theme theme;
|
||||
theme.windowBg = MakeArgb(kBase03);
|
||||
@@ -186,7 +200,7 @@ namespace jdi
|
||||
theme.cardBg = MakeArgb(kBase03);
|
||||
theme.plotBg = MakeArgb(kBase03);
|
||||
theme.border = MakeArgb(0x18505Fu);
|
||||
theme.borderStrong = MakeArgb(kBase01);
|
||||
theme.borderStrong = MakeArgb(0x205363u);
|
||||
|
||||
theme.text = MakeArgb(kBase0);
|
||||
theme.textStrong = MakeArgb(kBase1);
|
||||
@@ -199,8 +213,11 @@ namespace jdi
|
||||
theme.pressed = MakeArgb(0x0A3F4Cu);
|
||||
theme.selection = MakeArgb(0x12414Fu);
|
||||
|
||||
theme.gridWeak = MakeArgb(0x0B3A46u);
|
||||
theme.gridStrong = MakeArgb(0x14495Au);
|
||||
// 网格叠底与绘图区底色同为 base03 时看不出网格层次 ⇒ 显式给出略亮一档的叠底,
|
||||
// 结构色按**它**选(对比度 1.30 / 1.70 / 2.10)。
|
||||
theme.laneBg = MakeArgb(0x032E38u);
|
||||
theme.gridWeak = MakeArgb(0x13414Cu);
|
||||
theme.gridStrong = MakeArgb(0x205363u);
|
||||
theme.axisLine = MakeArgb(kBase01);
|
||||
theme.cursor = MakeArgb(kOrange);
|
||||
theme.readoutBg = MakeArgb(kBase02);
|
||||
@@ -316,10 +333,14 @@ namespace jdi
|
||||
|
||||
std::uint32_t ThemeLaneBackground(const Theme& theme)
|
||||
{
|
||||
// 网格底色必须与绘图区底色**可辨**(否则几十条网格看起来是一整片)。
|
||||
// 用统一的对比度阈值 1.03:本就有差异的主题(Solarised Light / Light / Dark)
|
||||
// 逐位返回 `cardBg`;两侧完全同色的主题(Solarised Dark 的 base03)会被轻轻推开。
|
||||
return EnsureContrast(theme.plotBg | 0xFF000000u, theme.cardBg | 0xFF000000u, 1.03);
|
||||
// 网格叠底色是**主题配色的一部分**(`Theme::laneBg`),由 `FinalizeTheme` 在构建主题时
|
||||
// 保证与绘图区底色可辨;这里只做"未配置"的兜底。
|
||||
if (theme.laneBg != 0u)
|
||||
{
|
||||
return theme.laneBg | 0xFF000000u;
|
||||
}
|
||||
const std::uint32_t plot = theme.plotBg | 0xFF000000u;
|
||||
return EnsureContrast(plot, theme.cardBg | 0xFF000000u, 1.03);
|
||||
}
|
||||
|
||||
const Theme& ThemeFor(ThemeKind kind)
|
||||
|
||||
Reference in new issue
Block a user