Files
Just-DataInspector/Components/Shared/Src/Theme.cpp
T
DeepSeek-V4.1-Flash bdd703b88b 主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能
① 主题自适应(`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` 第十六轮(含补丁脚本与编码/行尾踩坑)。
2026-10-30 03:10:51 +08:00

374 lines
13 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.
#include "Theme.hpp"
#include <algorithm>
#include <cmath>
namespace jdi
{
namespace
{
// Solarised Light 基础色板。
const std::uint32_t kBase03 = 0x002B36u; // #002B36
const std::uint32_t kBase02 = 0x073642u; // #073642
const std::uint32_t kBase01 = 0x586E75u; // #586E75
const std::uint32_t kBase00 = 0x657B83u; // #657B83
const std::uint32_t kBase0 = 0x839496u; // #839496
const std::uint32_t kBase1 = 0x93A1A1u; // #93A1A1
const std::uint32_t kBase2 = 0xEEE8D5u; // #EEE8D5
const std::uint32_t kBase3 = 0xFDF6E3u; // #FDF6E3
// Solarised 强调色。
const std::uint32_t kYellow = 0xB58900u; // #B58900
const std::uint32_t kOrange = 0xCB4B16u; // #CB4B16
const std::uint32_t kRed = 0xDC322Fu; // #DC322F
const std::uint32_t kMagenta = 0xD33682u; // #D33682
const std::uint32_t kViolet = 0x6C71C4u; // #6C71C4
const std::uint32_t kBlue = 0x268BD2u; // #268BD2
const std::uint32_t kCyan = 0x2AA198u; // #2AA198
const std::uint32_t kGreen = 0x859900u; // #859900
/** @brief 波形默认配色,按视觉对比度排序。 */
const std::uint32_t kSignalPalette[] = {
MakeArgb(kBlue), MakeArgb(kGreen), MakeArgb(kRed), MakeArgb(kViolet),
MakeArgb(kYellow), MakeArgb(kCyan), MakeArgb(kMagenta), MakeArgb(kOrange),
MakeArgb(kBase01), MakeArgb(kBase0),
};
/**
* @brief 把绘图区内部的"结构性"颜色按**底色**自动校正到足够对比(见 `EnsureContrast`)。
*
* 阈值(对比度)取"Solarised Light 现有配色刚好达标"的水位:
* 该主题的历史配色是人工调过的,达标时 `EnsureContrast` 逐位返回原色 ⇒ 默认主题
* **一个像素都不会变**;其它主题(尤其深色)则自动把网格线/时间轴/分界线提亮。
*
* | 字段 | 阈值 | Solarised Light 现状 |
* | --- | ---: | ---: |
* | `gridWeak`(网格主刻线 + 五等分线基色) | 1.30 | 约 1.38 |
* | `gridStrong`(零线/加重线) | 1.65 | 约 1.80 |
* | `axisLine`(时间轴、网格分界线、绘图区外框) | 2.10 | 约 2.43 |
* | `borderStrong`(网格间分界线) | 1.70 | 约 2.43 |
*/
Theme NormalizePlotContrast(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);
return theme;
}
/** @brief 全局唯一主题实例(Solarised Light;历史默认主题)。 */
const Theme kSolarizedLight = NormalizePlotContrast([]
{
Theme theme;
theme.windowBg = MakeArgb(kBase3);
theme.toolbarBg = MakeArgb(kBase2);
theme.panelBg = MakeArgb(kBase2);
theme.cardBg = MakeArgb(kBase3);
theme.plotBg = MakeArgb(kBase3);
theme.border = MakeArgb(0xD9D1BCu);
theme.borderStrong = MakeArgb(kBase1);
theme.text = MakeArgb(kBase00);
theme.textStrong = MakeArgb(kBase02);
theme.textMuted = MakeArgb(kBase1);
theme.textOnAccent = MakeArgb(kBase3);
theme.accent = MakeArgb(kBlue);
theme.accentSoft = MakeArgb(0xCFE4F3u);
theme.hover = MakeArgb(0xE4DCC6u);
theme.pressed = MakeArgb(0xD8CFB6u);
theme.selection = MakeArgb(0xDCEAF6u);
// 网格线需要在绘图区底色(kBase3)上清晰可辨,同时又要与网格边界
// (axisLine/borderStrong 的蓝灰)明显区分——因此采用偏暖的加深米色。
theme.gridWeak = MakeArgb(0xDDD3B8u);
theme.gridStrong = MakeArgb(0xC3B896u);
theme.axisLine = MakeArgb(kBase1);
theme.cursor = MakeArgb(kOrange);
theme.readoutBg = MakeArgb(0xF6EFDCu);
theme.danger = MakeArgb(kRed);
theme.warning = MakeArgb(kYellow);
return theme;
}());
/**
* @brief 浅色主题(参考 VS Code 2026 的默认浅色配色)。
*
* 与 Solarised Light 的区别:底色是**中性白/浅灰**而不是暖米色,
* 强调色是 VS Code 的系统蓝 `#0078D4`,描边是中性灰。
*/
const Theme kLight = NormalizePlotContrast([]
{
Theme theme;
theme.windowBg = MakeArgb(0xFFFFFFu); // editor.background
theme.toolbarBg = MakeArgb(0xF8F8F8u); // titleBar/sideBar
theme.panelBg = MakeArgb(0xF8F8F8u);
theme.cardBg = MakeArgb(0xFFFFFFu);
theme.plotBg = MakeArgb(0xFFFFFFu);
theme.border = MakeArgb(0xE5E5E5u); // panel.border
theme.borderStrong = MakeArgb(0xCECECEu);
theme.text = MakeArgb(0x3B3B3Bu); // foreground
theme.textStrong = MakeArgb(0x1F1F1Fu);
theme.textMuted = MakeArgb(0x8B8B8Bu); // descriptionForeground
theme.textOnAccent = MakeArgb(0xFFFFFFu);
theme.accent = MakeArgb(0x0078D4u); // button.background
theme.accentSoft = MakeArgb(0xE5F1FBu);
theme.hover = MakeArgb(0xF2F2F2u); // list.hoverBackground
theme.pressed = MakeArgb(0xE8E8E8u);
theme.selection = MakeArgb(0xE8E8E8u);
// 白底上网格线必须够淡(否则抢波形),但主刻线要能与次级线区分。
// ⚠️ 这三个颜色会被 `NormalizePlotContrast` 按白底自动校正到可见水位。
theme.gridWeak = MakeArgb(0xEDEDEDu);
theme.gridStrong = MakeArgb(0xD6D6D6u);
theme.axisLine = MakeArgb(0xCECECEu);
theme.cursor = MakeArgb(0xD18616u); // VS Code charts.orange
theme.readoutBg = MakeArgb(0xF3F3F3u);
theme.danger = MakeArgb(0xE51400u); // errorForeground
theme.warning = MakeArgb(0xBF8803u);
return theme;
}());
/**
* @brief 深色主题(参考 VS Code Dark+)。
*/
const Theme kDark = NormalizePlotContrast([]
{
Theme theme;
theme.windowBg = MakeArgb(0x1E1E1Eu); // editor.background
theme.toolbarBg = MakeArgb(0x252526u); // sideBar.background
theme.panelBg = MakeArgb(0x252526u);
theme.cardBg = MakeArgb(0x2D2D30u);
theme.plotBg = MakeArgb(0x1E1E1Eu);
theme.border = MakeArgb(0x3C3C3Cu);
theme.borderStrong = MakeArgb(0x4A4A4Au);
theme.text = MakeArgb(0xCCCCCCu);
theme.textStrong = MakeArgb(0xFFFFFFu);
theme.textMuted = MakeArgb(0x8C8C8Cu);
theme.textOnAccent = MakeArgb(0xFFFFFFu);
theme.accent = MakeArgb(0x007ACCu); // focusBorder / statusBar
theme.accentSoft = MakeArgb(0x094771u); // list.activeSelectionBackground
theme.hover = MakeArgb(0x2A2D2Eu); // list.hoverBackground
theme.pressed = MakeArgb(0x37373Du);
theme.selection = MakeArgb(0x04395Eu);
// ⚠️ 深色底上的原始取值几乎与底色同亮度(差 2~3 个色阶)⇒ 交给
// `NormalizePlotContrast` 按 `plotBg` 自动提亮到可见水位。
theme.gridWeak = MakeArgb(0x2B2B2Bu);
theme.gridStrong = MakeArgb(0x3A3A3Au);
theme.axisLine = MakeArgb(0x4A4A4Au);
theme.cursor = MakeArgb(0xD18616u);
theme.readoutBg = MakeArgb(0x2D2D30u);
theme.danger = MakeArgb(0xF44747u);
theme.warning = MakeArgb(0xCCA700u);
return theme;
}());
/**
* @brief Solarised Dark(参考 VS Code 的 Solarized Dark)。
*
* 底色用 base03、面板用 base02,强调色仍是 Solarised 那一套 —— 因此波形配色表
* (`SignalPalette`)与 Solarised Light 完全一致。
*/
const Theme kSolarizedDark = NormalizePlotContrast([]
{
Theme theme;
theme.windowBg = MakeArgb(kBase03);
theme.toolbarBg = MakeArgb(kBase02);
theme.panelBg = MakeArgb(kBase02);
theme.cardBg = MakeArgb(kBase03);
theme.plotBg = MakeArgb(kBase03);
theme.border = MakeArgb(0x18505Fu);
theme.borderStrong = MakeArgb(kBase01);
theme.text = MakeArgb(kBase0);
theme.textStrong = MakeArgb(kBase1);
theme.textMuted = MakeArgb(kBase01);
theme.textOnAccent = MakeArgb(kBase03);
theme.accent = MakeArgb(kBlue);
theme.accentSoft = MakeArgb(0x12414Fu);
theme.hover = MakeArgb(0x10505Fu);
theme.pressed = MakeArgb(0x0A3F4Cu);
theme.selection = MakeArgb(0x12414Fu);
theme.gridWeak = MakeArgb(0x0B3A46u);
theme.gridStrong = MakeArgb(0x14495Au);
theme.axisLine = MakeArgb(kBase01);
theme.cursor = MakeArgb(kOrange);
theme.readoutBg = MakeArgb(kBase02);
theme.danger = MakeArgb(kRed);
theme.warning = MakeArgb(kYellow);
return theme;
}());
/** @brief 全局「当前主题」指针(由宿主设置,见 `SetCurrentTheme`)。 */
const Theme* g_currentTheme = &kSolarizedLight;
}
double SrgbLuminance(std::uint32_t argb)
{
// sRGB → 线性光:> 0.03928 用 2.4 次幂,否则用 12.92 线性段(WCAG 2.x)。
const auto channel = [](std::uint32_t value) -> double
{
const double c = static_cast<double>(value & 0xFFu) / 255.0;
return (c <= 0.03928) ? (c / 12.92) : std::pow((c + 0.055) / 1.055, 2.4);
};
const double r = channel((argb >> 16) & 0xFFu);
const double g = channel((argb >> 8) & 0xFFu);
const double b = channel(argb & 0xFFu);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
double ContrastRatio(std::uint32_t firstColor, std::uint32_t secondColor)
{
const double first = SrgbLuminance(firstColor);
const double second = SrgbLuminance(secondColor);
const double high = std::max(first, second);
const double low = std::min(first, second);
return (high + 0.05) / (low + 0.05);
}
std::uint32_t EnsureContrast(std::uint32_t background, std::uint32_t foreground, double minRatio)
{
if (!(minRatio > 1.0) || ContrastRatio(background, foreground) >= minRatio)
{
return foreground; // 已达标(或不限制):**逐位返回原色**
}
// 远离底色:深底就提亮、浅底就加深。二分求解"刚好达标"的混色量,
// 使输出随底色**连续**变化(不会在阈值附近两个极端色之间跳)。
const bool lighten = SrgbLuminance(background) < 0.5;
const std::uint32_t extreme = lighten ? 0xFFFFFFFFu : 0xFF000000u;
double low = 0.0;
double high = 1.0;
for (int iteration = 0; iteration < 18; ++iteration)
{
const double middle = (low + high) * 0.5;
if (ContrastRatio(background, MixColor(foreground, extreme, middle)) < minRatio)
{
low = middle;
}
else
{
high = middle;
}
}
return MixColor(foreground | 0xFF000000u, extreme, high);
}
std::uint32_t MixToLuminance(std::uint32_t color, double targetLuminance)
{
const std::uint32_t base = color | 0xFF000000u;
const double current = SrgbLuminance(base);
if (!(targetLuminance > 0.0))
{
targetLuminance = 0.0;
}
if (targetLuminance > 1.0)
{
targetLuminance = 1.0;
}
if (std::fabs(targetLuminance - current) < 1.0e-4)
{
return base;
}
// 亮度沿"混向白 / 混向黑"单调 ⇒ 二分解出混色量;
// 目标亮度等于当前亮度时混色量为 0(等价返回原色),因此两侧在该点**连续衔接**。
const std::uint32_t extreme = (targetLuminance > current) ? 0xFFFFFFFFu : 0xFF000000u;
double low = 0.0;
double high = 1.0;
for (int iteration = 0; iteration < 20; ++iteration)
{
const double middle = (low + high) * 0.5;
const double luminance = SrgbLuminance(MixColor(base, extreme, middle));
if (targetLuminance > current)
{
if (luminance < targetLuminance)
{
low = middle;
}
else
{
high = middle;
}
}
else
{
if (luminance > targetLuminance)
{
low = middle;
}
else
{
high = middle;
}
}
}
return MixColor(base, extreme, high);
}
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);
}
const Theme& ThemeFor(ThemeKind kind)
{
switch (kind)
{
case ThemeKind::LIGHT:
return kLight;
case ThemeKind::DARK:
return kDark;
case ThemeKind::SOLARIZED_DARK:
return kSolarizedDark;
case ThemeKind::SOLARIZED_LIGHT:
default:
return kSolarizedLight;
}
}
const wchar_t* ThemeName(ThemeKind kind)
{
switch (kind)
{
case ThemeKind::LIGHT:
return L"Light";
case ThemeKind::DARK:
return L"Dark";
case ThemeKind::SOLARIZED_DARK:
return L"Solarised Dark";
case ThemeKind::SOLARIZED_LIGHT:
default:
return L"Solarised Light";
}
}
const Theme& CurrentTheme()
{
return (g_currentTheme != nullptr) ? *g_currentTheme : kSolarizedLight;
}
void SetCurrentTheme(const Theme* theme)
{
g_currentTheme = (theme != nullptr) ? theme : &kSolarizedLight;
}
const std::uint32_t* SignalPalette(int& count)
{
count = static_cast<int>(sizeof(kSignalPalette) / sizeof(kSignalPalette[0]));
return kSignalPalette;
}
} // namespace jdi