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…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
252 lines
9.8 KiB
C++
252 lines
9.8 KiB
C++
#ifndef __THEME_HPP__
|
||
#define __THEME_HPP__
|
||
|
||
#include <windows.h>
|
||
#include <objidl.h>
|
||
#include <gdiplus.h>
|
||
|
||
#include <cstdint>
|
||
|
||
/// @brief GDI+ 命名空间短别名,便于在界面代码中引用绘图类型。
|
||
namespace gd = Gdiplus;
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
/**
|
||
* @brief 将 0xAARRGGBB 颜色值转换为 GDI+ 颜色对象。
|
||
* @param [IN] argb 颜色值。
|
||
* @return GDI+ 颜色对象。
|
||
*/
|
||
inline gd::Color ToGdiColor(std::uint32_t argb)
|
||
{
|
||
return gd::Color(static_cast<BYTE>((argb >> 24) & 0xFFu), static_cast<BYTE>((argb >> 16) & 0xFFu),
|
||
static_cast<BYTE>((argb >> 8) & 0xFFu), static_cast<BYTE>(argb & 0xFFu));
|
||
}
|
||
|
||
/**
|
||
* @brief 由 0xRRGGBB 构造不透明的 0xAARRGGBB 颜色值。
|
||
* @param [IN] rgb RGB 颜色值。
|
||
* @return 不透明颜色值。
|
||
*/
|
||
inline std::uint32_t MakeArgb(std::uint32_t rgb)
|
||
{
|
||
return 0xFF000000u | (rgb & 0x00FFFFFFu);
|
||
}
|
||
|
||
/**
|
||
* @brief 替换颜色值的 Alpha 通道。
|
||
* @param [IN] argb 原颜色值。
|
||
* @param [IN] alpha 新的 Alpha 值(0-255)。
|
||
* @return 新的颜色值。
|
||
*/
|
||
inline std::uint32_t WithAlpha(std::uint32_t argb, unsigned int alpha)
|
||
{
|
||
return (argb & 0x00FFFFFFu) | ((static_cast<std::uint32_t>(alpha) & 0xFFu) << 24);
|
||
}
|
||
|
||
/**
|
||
* @brief 在两个颜色之间做线性插值。
|
||
* @param [IN] fromColor 起始颜色。
|
||
* @param [IN] toColor 结束颜色。
|
||
* @param [IN] ratio 插值比例,0 返回起始色,1 返回结束色。
|
||
* @return 插值后的颜色值。
|
||
*/
|
||
inline std::uint32_t MixColor(std::uint32_t fromColor, std::uint32_t toColor, double ratio)
|
||
{
|
||
if (ratio < 0.0)
|
||
{
|
||
ratio = 0.0;
|
||
}
|
||
if (ratio > 1.0)
|
||
{
|
||
ratio = 1.0;
|
||
}
|
||
|
||
std::uint32_t result = 0;
|
||
for (int shift = 0; shift <= 24; shift += 8)
|
||
{
|
||
const double a = static_cast<double>((fromColor >> shift) & 0xFFu);
|
||
const double b = static_cast<double>((toColor >> shift) & 0xFFu);
|
||
const std::uint32_t mixed = static_cast<std::uint32_t>(a + (b - a) * ratio + 0.5);
|
||
result |= ((mixed & 0xFFu) << shift);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 颜色的**相对亮度**(WCAG 2.x 定义:sRGB 线性化后按 0.2126/0.7152/0.0722 加权)。
|
||
* @param [IN] argb 颜色值(只看 RGB 通道)。
|
||
* @return 相对亮度,取值 0(黑)… 1(白)。
|
||
*/
|
||
double SrgbLuminance(std::uint32_t argb);
|
||
|
||
/**
|
||
* @brief 两色之间的**对比度**(WCAG 2.x 定义:`(L1 + 0.05) / (L2 + 0.05)`)。
|
||
* @param [IN] firstColor 第一色。
|
||
* @param [IN] secondColor 第二色。
|
||
* @return 对比度,取值 1(完全相同)… 21(黑白)。
|
||
*/
|
||
double ContrastRatio(std::uint32_t firstColor, std::uint32_t secondColor);
|
||
|
||
/**
|
||
* @brief 保证前景色相对底色有**足够对比度**:不足时沿"远离底色"的方向混向黑/白。
|
||
*
|
||
* 用途:绘图区内部的网格线、时间轴等分线、分界线等原本按某一个主题手工调色的元素,
|
||
* 必须跟着主题背景色自动适配(浅底上加深、深底上提亮),否则换到深色主题后
|
||
* 网格线只剩几个色阶、几乎看不见。
|
||
*
|
||
* ⚠️ **已达标时逐位返回原色**:Solarised Light 的历史配色是人工调过的,
|
||
* 只要它满足阈值就必须保持逐像素不变(避免"换主题修复"顺带改变默认主题观感)。
|
||
*
|
||
* @param [IN] background 底色(不透明)。
|
||
* @param [IN] foreground 前景色(不透明)。
|
||
* @param [IN] minRatio 期望的最小对比度(≥ 1,1 表示不限制)。
|
||
* @return 满足阈值的颜色(RGB 不变、alpha = 0xFF)。
|
||
*/
|
||
std::uint32_t EnsureContrast(std::uint32_t background, std::uint32_t foreground, double minRatio);
|
||
|
||
/**
|
||
* @brief 把颜色沿"亮度"插值到目标相对亮度(保持色相,二分求解混色比例)。
|
||
*
|
||
* 比"固定比例混向黑/白"更可控:调用方指定**目标亮度**,本函数负责求出混色量。
|
||
* 目标亮度高于当前亮度时混向白、低于时混向黑;求解连续 ⇒ 输入微调时输出也连续
|
||
*(不会在两个极端色之间跳变)。
|
||
*
|
||
* @param [IN] color 原色(不透明)。
|
||
* @param [IN] targetLuminance 目标相对亮度(0…1)。
|
||
* @return 亮度接近目标值的颜色(无法达到时取该方向上的极限)。
|
||
*/
|
||
std::uint32_t MixToLuminance(std::uint32_t color, double targetLuminance);
|
||
|
||
/**
|
||
* @brief 界面主题色板。
|
||
*
|
||
* ## 结构色的对比度约定(重要,改配色前必读)
|
||
*
|
||
* `gridWeak` / `gridStrong` / `axisLine` / `borderStrong` 这些“结构色”是**主题配色的一部分**
|
||
* (每个主题各自给值),它们画在**网格叠底**(`laneBg`)之上,而不是绘图区底色 `plotBg`。
|
||
* 因此它们的最小对比度一律针对 `laneBg` 校验:
|
||
*
|
||
* | 字段 | 最小对比度(相对 `laneBg`) |
|
||
* | --- | ---: |
|
||
* | `gridWeak` | 1.30 |
|
||
* | `gridStrong` | 1.70 |
|
||
* | `axisLine` | 2.10 |
|
||
* | `borderStrong` | 1.70 |
|
||
*
|
||
* 各主题给出的值本就满足上述下限,因此最终化时**逐位返回原色**、不做任何改色;
|
||
* 这些阈值只是“不允许配置出看不见的网格线”的兜底。
|
||
*/
|
||
struct Theme
|
||
{
|
||
std::uint32_t windowBg = 0; /**< 窗口底色。 */
|
||
std::uint32_t toolbarBg = 0; /**< 顶部工具栏底色。 */
|
||
std::uint32_t panelBg = 0; /**< 侧边面板底色。 */
|
||
std::uint32_t cardBg = 0; /**< 面板内卡片底色。 */
|
||
std::uint32_t plotBg = 0; /**< 绘图区底色。 */
|
||
std::uint32_t border = 0; /**< 常规描边色。 */
|
||
std::uint32_t borderStrong = 0; /**< 强调描边色。 */
|
||
|
||
std::uint32_t text = 0; /**< 正文文字色。 */
|
||
std::uint32_t textStrong = 0; /**< 强调文字色。 */
|
||
std::uint32_t textMuted = 0; /**< 次要文字色。 */
|
||
std::uint32_t textOnAccent = 0; /**< 强调色之上的文字色。 */
|
||
|
||
std::uint32_t accent = 0; /**< 主题强调色。 */
|
||
std::uint32_t accentSoft = 0; /**< 强调色的浅色版本。 */
|
||
std::uint32_t hover = 0; /**< 悬停底色。 */
|
||
std::uint32_t pressed = 0; /**< 按下底色。 */
|
||
std::uint32_t selection = 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; /**< 警示色。 */
|
||
std::uint32_t warning = 0; /**< 提示色。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 内置主题配色种类。
|
||
*
|
||
* 顺序即「主题配色」二级菜单里的显示顺序(用户 2026-10-29 指定)。
|
||
*/
|
||
enum class ThemeKind : int
|
||
{
|
||
LIGHT = 0, /**< 浅色(参考 VS Code 2026 默认浅色主题)。 */
|
||
SOLARIZED_LIGHT = 1, /**< Solarised Light(历史默认主题)。 */
|
||
DARK = 2, /**< 深色(参考 VS Code Dark+)。 */
|
||
SOLARIZED_DARK = 3 /**< Solarised Dark(参考 VS Code 的 Solarized Dark)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 取某个内置主题的配色(静态实例,地址稳定)。
|
||
* @param [IN] kind 主题种类;越界时退回 Solarised Light。
|
||
* @return 主题常引用。
|
||
*/
|
||
const Theme& ThemeFor(ThemeKind kind);
|
||
|
||
/**
|
||
* @brief 取主题在「主题配色」菜单里的显示名称。
|
||
* @param [IN] kind 主题种类。
|
||
* @return 名称(静态字符串,无需释放)。
|
||
*/
|
||
const wchar_t* ThemeName(ThemeKind kind);
|
||
|
||
/**
|
||
* @brief 全局「当前主题」指针。
|
||
*
|
||
* ⚠️ 为什么需要全局:`UiControls` 里少数几个原语(色块描边、非主题化的默认底色)
|
||
* 没有 `Theme` 形参,却必须跟随用户选择的主题。宿主在启动与切换主题时调用
|
||
* `SetCurrentTheme(&m_theme)`,把**宿主自己那个** `Theme` 实例的地址交给它 ——
|
||
* 这样切换主题只需修改 `m_theme` 的内容,不需要通知每一个组件刷新指针。
|
||
*
|
||
* @return 当前主题;未设置时返回 Solarised Light。
|
||
*/
|
||
const Theme& CurrentTheme();
|
||
|
||
/**
|
||
* @brief 设置全局「当前主题」。
|
||
* @param [IN] theme 主题实例(必须比使用期更长;传 nullptr 表示恢复默认)。
|
||
*/
|
||
void SetCurrentTheme(const Theme* theme);
|
||
|
||
/**
|
||
* @brief 取「绘图区网格底色」——**网格叠底**(每条网格内部的底色)。
|
||
*
|
||
* 它与 `Theme::plotBg`(绘图区整体底色)分开:网格之外的时间轴/留白区用 `plotBg`,
|
||
* 网格内部用本函数。当两者过于接近时,本函数会把网格底色**轻轻地**往远离绘图区
|
||
* 底色的方向挪一点(仅供深色主题的 Solarised Dark 这类"两者完全同色"的情形),
|
||
* 保证相邻网格仍能被分界线以外的底色层次区分开。
|
||
*
|
||
* ⚠️ 两者本来就有可辨识的亮度差时**逐位返回 `cardBg`**(Solarised Light / Light / Dark
|
||
* 的默认观感不变)。
|
||
*
|
||
* @param [IN] theme 主题。
|
||
* @return 网格底色(不透明)。
|
||
*/
|
||
std::uint32_t ThemeLaneBackground(const Theme& theme);
|
||
|
||
/**
|
||
* @brief 获取波形默认配色表。
|
||
* @param [OUT] count 配色数量输出。
|
||
* @return 颜色数组首地址。
|
||
*/
|
||
const std::uint32_t* SignalPalette(int& count);
|
||
|
||
} // namespace jdi
|
||
|
||
#endif // __THEME_HPP__
|