#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 主题**最终化**:确定网格叠底色,并保证结构色在**它**之上可辨。 * * ⚠️⚠️ **参照底色必须是 `laneBg`(网格叠底),不是 `plotBg`**: * 网格线画在每条网格内部的叠底上。深色主题里 `plotBg`(#1E1E1E)与 `laneBg` * (#2D2D30)亮度差得远,只按 `plotBg` 校正会让网格线落进叠底色的亮度范围 * ⇒ 实测对比度只有 **1.07**(几乎看不见,用户 2026-10-30 报告)。 * * 阈值取"Solarised Light 现有配色刚好达标"的水位(针对 `laneBg` 实测: * 1.335 / 1.771 / 2.395 / 2.395),因此默认主题**逐位不变**;其它主题若不足才被校正。 * * | 字段 | 阈值(相对 `laneBg`) | Solarised Light 现状 | * | --- | ---: | ---: | * | `gridWeak`(网格主刻线 + 五等分线基色) | 1.30 | 1.335 | * | `gridStrong`(零线/加重线) | 1.70 | 1.771 | * | `axisLine`(时间轴、网格分界线、绘图区外框) | 2.10 | 2.395 | * | `borderStrong`(网格间分界线) | 1.70 | 2.395 | */ Theme FinalizeTheme(Theme theme) { const std::uint32_t plot = theme.plotBg | 0xFF000000u; // 网格叠底:未显式配置时回退到 `cardBg`,并保证与绘图区底色可辨(1.03)。 const std::uint32_t laneSource = (theme.laneBg != 0u) ? (theme.laneBg | 0xFF000000u) : (theme.cardBg | 0xFF000000u); theme.laneBg = EnsureContrast(plot, laneSource, 1.03); theme.gridWeak = EnsureContrast(theme.laneBg, theme.gridWeak | 0xFF000000u, 1.30); theme.gridStrong = EnsureContrast(theme.laneBg, theme.gridStrong | 0xFF000000u, 1.70); theme.axisLine = EnsureContrast(theme.laneBg, theme.axisLine | 0xFF000000u, 2.10); theme.borderStrong = EnsureContrast(theme.laneBg, theme.borderStrong | 0xFF000000u, 1.70); return theme; } /** @brief 全局唯一主题实例(Solarised Light;历史默认主题)。 */ const Theme kSolarizedLight = FinalizeTheme([] { 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 推暗 1.03 ≈ #F9F2E0)上清晰可辨,同时又要与 // 网格边界(axisLine/borderStrong 的蓝灰)明显区分——因此采用偏暖的加深米色。 // 这三个值相对叠底色的对比度分别是 1.335 / 1.771 / 2.395,已达标 ⇒ 最终化不改动。 theme.laneBg = MakeArgb(0xF9F2E0u); 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 = FinalizeTheme([] { 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(0xC3C3C3u); 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); // 网格叠底比绘图区白底略暗一档,便于分辨相邻网格; // 结构色按**叠底色**选中(对比度:1.30 / 1.70 / 2.10),不要再用原白底的值。 theme.laneBg = MakeArgb(0xFBFBFBu); theme.gridWeak = MakeArgb(0xDEDEDEu); theme.gridStrong = MakeArgb(0xC3C3C3u); theme.axisLine = MakeArgb(0xAFAFAFu); 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 = FinalizeTheme([] { 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(0x505050u); 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); // ⚠️ 深色主题的结构色**必须比旧值亮得多**:它们画在网格叠底(#2D2D30)上, // 旧值(#2B2B2B / #3A3A3A)与叠底几乎同亮度(实测对比度 1.07)⇒ 网格线看不见。 // 这里的值按叠底色选:1.30 / 1.70 / 2.10。 theme.laneBg = MakeArgb(0x2D2D30u); theme.gridWeak = MakeArgb(0x3F3F3Fu); theme.gridStrong = MakeArgb(0x505050u); theme.axisLine = MakeArgb(0x5E5E5Eu); 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 = FinalizeTheme([] { 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(0x205363u); 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); // 网格叠底与绘图区底色同为 base03 时看不出网格层次 ⇒ 显式给出略亮一档的叠底, // 结构色按**它**选(对比度 1.30 / 1.70 / 2.10)。 theme.laneBg = MakeArgb(0x032E38u); theme.gridWeak = MakeArgb(0x13414Cu); theme.gridStrong = MakeArgb(0x205363u); 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 = &kLight; } 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) { // 网格叠底色是**主题配色的一部分**(`Theme::laneBg`),由 `FinalizeTheme` 在构建主题时 // 保证与绘图区底色可辨;这里只做"未配置"的兜底。 if (theme.laneBg != 0u) { return theme.laneBg | 0xFF000000u; } const std::uint32_t plot = theme.plotBg | 0xFF000000u; return EnsureContrast(plot, theme.cardBg | 0xFF000000u, 1.03); } const Theme& ThemeFor(ThemeKind kind) { switch (kind) { case ThemeKind::DARK: return kDark; case ThemeKind::SOLARIZED_DARK: return kSolarizedDark; case ThemeKind::SOLARIZED_LIGHT: return kSolarizedLight; case ThemeKind::LIGHT: default: // 默认主题 = Light(用户 2026-11-02 要求;旧默认是 Solarised Light)。 return kLight; } } const wchar_t* ThemeName(ThemeKind kind) { switch (kind) { case ThemeKind::DARK: return L"Dark"; case ThemeKind::SOLARIZED_DARK: return L"Solarised Dark"; case ThemeKind::SOLARIZED_LIGHT: return L"Solarised Light"; case ThemeKind::LIGHT: default: return L"Light"; } } const Theme& CurrentTheme() { return (g_currentTheme != nullptr) ? *g_currentTheme : kLight; } void SetCurrentTheme(const Theme* theme) { g_currentTheme = (theme != nullptr) ? theme : &kLight; } const std::uint32_t* SignalPalette(int& count) { count = static_cast(sizeof(kSignalPalette) / sizeof(kSignalPalette[0])); return kSignalPalette; } } // namespace jdi