#include "Theme.hpp" #include #include 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(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(sizeof(kSignalPalette) / sizeof(kSignalPalette[0])); return kSignalPalette; } } // namespace jdi