① 主题自适应(`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` 第十六轮(含补丁脚本与编码/行尾踩坑)。
226 lines
8.4 KiB
C++
226 lines
8.4 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 界面主题色板(Solarised Light 派生)。
|
||
*/
|
||
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; /**< 坐标轴线颜色。 */
|
||
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__
|