Files
Just-DataInspector/Components/Shared/Inc/Theme.hpp
T
DeepSeek V4.1 Flash 2d83b494a3 均值面板越界根因 + 网格线进入主题配色 + 条目栏悬停/整行点击 + 均值点数下限 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…上限)」「命中要求行完全落在内容区里」两条改成现行做法。
2026-10-30 10:51:39 +08:00

252 lines
9.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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__