Files
DeepSeek V4.1 Flash 8de8a8fb85 预览曲线按面板高亮 + 统计数字不再卡在“统计中…” + 中心轴面板改表格 + 默认主题 Light + 标签页 DPI 修复 + 布局边界幽灵元素
一、预览曲线悬停高亮(只开弱峰+中心轴时无反应;三个全开时总亮均值曲线)
   根因:PaintLaneDerivedOverlays 里只有 MEAN 分支传 highlight,且悬停状态没有记录面板种类。
   修法:新增 m_meanRowHoverKind,高亮条件 = 悬停行 == 本行 && 面板种类 == 本曲线来源;
         条目栏强调色同样按种类门控;悬停种类进入面板缓存键。

二、统计数字松手后停在“统计中…”
   根因:交互期间面板缓存键被冻结(为拖动跟手),冻结期满后没有重绘请求(鼠标已停、
         统计也已完成),最后一次绘制仍是冻结帧。
   修法:记录 m_panelKeysFrozen 并公开 PanelRefreshPending();Render 末尾把它并入
         滚动动画定时器的武装条件 ⇒ 空闲后自动补一帧真实数字,随后停表。

三、「中心轴边沿计数」面板改为表格
   表头固定:信号名 / 上升沿计数 / 下降沿计数;每行底色 = 该信号配色(未勾选的行普通底色),
   三列与表头严格对齐,内容可滚动,单击行展开两行参数滑块。
   - EdgePanelLayout 新增 tableHeader / tableColumns[3];EdgePanelLayoutFor 新增 centerTable 分支;
   - 新增 EdgeCenterTableHeader() / EdgeCountCellText();EnsureEdgeCenterItem 默认 collapsed;
   - PanelSlotKey 的 CENTER 分支改为按 item 判断并混合计数(表格已没有 rowStats)。

四、默认主题改为 Light(Application、ThemeFor/ThemeName 默认分支、CurrentTheme 兜底)。

五、标签页文本在 DPI 切换后被省略
   根因:标签宽度恰好等于“内边距+文本+间距+关闭按钮”,无任何余量;TabItemWidget 缓存了
         Configure 时的 scale 并用它反推可用文本宽度,而 TabBar::SetScale 不重建控件树
         ⇒ 125%→100% 后可用宽度少约 4.5px ⇒ 吃掉末字。
   修法:SetScale 变化时重建控件树;并把文本可用矩形改由 ArrangeWidgets 统一下发
         (SetState 增加 textRect 参数),绘制侧不再自行反推。

六、布局边界里的幽灵元素
   - 新增 SignalPanel::HasStyleTarget();BuildBoxes 与 QueryCursor 在无样式目标时
     不再上报/响应色块、样式、调整、线宽、透明度等控件;
   - Paint 的“无网格”分支显式清空 m_legendHits / m_cursorLabelRects / m_cursorSpanRect /
     m_freeMarkerHits / m_panelSlots / m_panelOrder(关闭整个分组后不再残留边界框);
   - 浮动面板之上的滚动条 / 回到最上方 / 内联输入框改为按可见性上报几何。

验证(离屏探针,SW_HIDE + 合成消息,不占用鼠标键盘屏幕)
  .preflight/p21.exe 9/9   .preflight/p22.exe 14/14   .preflight/p23.exe 7/7
  .preflight/p20 系列(9/11/5)全部回归通过
  make release / make debug 零 warning;Release 813,568 B
2026-11-02 15:22:26 +08:00

396 lines
14 KiB
C++
Raw Permalink 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 主题**最终化**:确定网格叠底色,并保证结构色在**它**之上可辨。
*
* ⚠️⚠️ **参照底色必须是 `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<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)
{
// 网格叠底色是**主题配色的一部分**(`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<int>(sizeof(kSignalPalette) / sizeof(kSignalPalette[0]));
return kSignalPalette;
}
} // namespace jdi