主题自适应配色 + 预览曲线连续配色 + 样式区复位按钮 + 均值滤波面板重做 + 面板缓存环带残留 + 调试层序 + 自由标记选取与性能
① 主题自适应(`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` 第十六轮(含补丁脚本与编码/行尾踩坑)。
This commit is contained in:
1 parent
71195c4c48
commit
bdd703b88b
24 files changed
+1815
-256
No files matched your search
@@ -502,6 +502,20 @@ namespace jdi
|
||||
OPACITY_VALUE, /**< 不透明度数值框(可点击输入)。 */
|
||||
WIDTH_SLIDER, /**< 线宽滑块。 */
|
||||
WIDTH_VALUE, /**< 线宽数值框(可点击输入)。 */
|
||||
/**
|
||||
* @brief 线宽行右侧的**复位按钮**(把线宽恢复为该信号的默认值)。
|
||||
*
|
||||
* 默认值:普通信号 = `kDefaultSignalLineWidth`(1.0 逻辑像素),
|
||||
* 单数据点信号 = `kSinglePointSignalLineWidth`(最大线宽:散点半径随线宽增长)。
|
||||
* 图标与「系数/偏移」二级菜单标题栏的还原图标**完全同款**(`UiPaintResetIcon`)。
|
||||
*/
|
||||
WIDTH_RESET,
|
||||
/**
|
||||
* @brief 透明度行右侧的**复位按钮**(把不透明度恢复为 100%)。
|
||||
*
|
||||
* 与 `WIDTH_RESET` 同一套图标与悬停观感,两者右边界与内容列右缘对齐。
|
||||
*/
|
||||
OPACITY_RESET,
|
||||
GAIN_ADJUST, /**< 「系数」按钮(点击展开系数二级面板)。 */
|
||||
MIRROR_ADJUST, /**< 「镜像」按钮(点击展开镜像二级面板)。 */
|
||||
ABSOLUTE_TOGGLE, /**< 「绝对值」切换按钮。 */
|
||||
@@ -1228,6 +1242,21 @@ namespace jdi
|
||||
*/
|
||||
bool ApplyOpacityFromX(float x);
|
||||
|
||||
/**
|
||||
* @brief 把当前目标信号的**线宽恢复为默认值**(「线宽」行右侧复位按钮)。
|
||||
*
|
||||
* 默认值与新建信号时同一来源(`DefaultSignalLineWidth(finiteCount)`):
|
||||
* 普通信号 1.0 逻辑像素、**单数据点信号**取最大线宽(散点半径随线宽增长)。
|
||||
* @return 取值发生变化返回 true。
|
||||
*/
|
||||
bool ResetStyleLineWidth();
|
||||
|
||||
/**
|
||||
* @brief 把当前目标信号的**不透明度恢复为 100%**(「透明度」行右侧复位按钮)。
|
||||
* @return 取值发生变化返回 true。
|
||||
*/
|
||||
bool ResetStyleOpacity();
|
||||
|
||||
/**
|
||||
* @brief 断言某「样式」选项对当前目标信号是否可用(不可用时置灰且不可点)。
|
||||
*
|
||||
@@ -1285,6 +1314,15 @@ namespace jdi
|
||||
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
|
||||
gd::RectF m_widthTrackRect; /**< 线宽滑块轨道矩形。 */
|
||||
gd::RectF m_widthValueRect; /**< 线宽数值文本矩形(双击可输入)。 */
|
||||
/**
|
||||
* @brief 线宽行右侧的**复位按钮**矩形(正方形,右边界与内容列右缘对齐)。
|
||||
*
|
||||
* 点击把该信号的线宽恢复为默认值(单数据点信号与普通信号的默认线宽不同)。
|
||||
* 宽高为 0 表示当前不显示(没有目标信号)。
|
||||
*/
|
||||
gd::RectF m_widthResetRect;
|
||||
/** @brief 透明度行右侧的**复位按钮**矩形(与 `m_widthResetRect` 同一列、同一尺寸)。 */
|
||||
gd::RectF m_opacityResetRect;
|
||||
/** 「调整」行的三个等宽按钮(下标 = 内部常量 `kAdjust*`):0 = 系数/偏移,1 = 镜像,2 = 绝对值。 */
|
||||
std::array<gd::RectF, 3> m_adjustRects;
|
||||
|
||||
|
||||
@@ -174,8 +174,15 @@ namespace jdi
|
||||
inline const float kSwatchMaxGap = 10.0f; /**< 颜色色块最大间距(用于铺满内容列)。 */
|
||||
inline const float kSliderValueWidth = 52.0f; /**< 滑块行数值文本宽度(透明度与线宽一致)。 */
|
||||
inline const float kSliderValueGap = 8.0f; /**< 滑块轨道与数值文本间距。 */
|
||||
inline const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
|
||||
inline const float kAdjustGap = 6.0f; /**< 「调整」行三个按钮之间的间距。 */
|
||||
inline const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */ /**
|
||||
* @brief 「线宽 / 透明度」行右侧**复位按钮**的边长(逻辑像素)。
|
||||
*
|
||||
* 与「系数/偏移」二级菜单标题栏的还原图标同一观感(`UiPaintResetIcon`),
|
||||
* 因此尺寸也取同一量级;行高不足时会被夹到行高。
|
||||
*/
|
||||
inline const float kSliderResetSize = 16.0f;
|
||||
/** @brief 数值文本与复位按钮之间的间距。 */
|
||||
inline const float kSliderResetGap = 4.0f; inline const float kAdjustGap = 6.0f; /**< 「调整」行三个按钮之间的间距。 */
|
||||
inline const float kAdjustMenuGap = 4.0f; /**< 「系数/偏移」「镜像」二级面板与按钮左上角的额外间距。 */
|
||||
|
||||
/** 「调整」行的按钮下标(与 `m_adjustRects` 同序)。 */
|
||||
|
||||
@@ -119,8 +119,10 @@ namespace jdi
|
||||
}
|
||||
boxes.Add(m_widthTrackRect, 3);
|
||||
boxes.Add(m_widthValueRect, 3);
|
||||
boxes.Add(m_widthResetRect, 3);
|
||||
boxes.Add(m_opacityTrackRect, 3);
|
||||
boxes.Add(m_opacityValueRect, 3);
|
||||
boxes.Add(m_opacityResetRect, 3);
|
||||
|
||||
// ── 「目标」行(样式区顶部,展示当前选中信号) ────────────────────
|
||||
boxes.Add(m_targetRowRect, 2);
|
||||
@@ -972,19 +974,35 @@ namespace jdi
|
||||
}
|
||||
|
||||
// ---- 线宽行与透明度行:几何完全一致,保证纵向宽度对齐(线宽在上、透明度在下)----
|
||||
const float valueWidth = std::min(kSliderValueWidth * m_scale, contentWidth * 0.4f);
|
||||
const float trackWidth = std::max(24.0f, contentWidth - valueWidth - kSliderValueGap * m_scale);
|
||||
// 两行右端各有一个**复位按钮**(用户 2026-10-29 要求):
|
||||
// 按钮贴齐内容列右缘,数值文本与轨道在它左侧依次收缩 ⇒ 不会超出内容列。
|
||||
// 内容列太窄(放不下"轨道 + 数值 + 按钮")时整列不显示复位按钮,避免把滑块挤没。
|
||||
const float resetSize = (contentWidth >= kSliderResetSize * 3.0f * m_scale)
|
||||
? std::min(kSliderResetSize * m_scale, rowHeight)
|
||||
: 0.0f;
|
||||
const float resetGap = (resetSize > 0.0f) ? kSliderResetGap * m_scale : 0.0f;
|
||||
const float sliderWidth = contentWidth - (resetSize > 0.0f ? (resetSize + resetGap) : 0.0f);
|
||||
const float valueWidth = std::min(kSliderValueWidth * m_scale, sliderWidth * 0.4f);
|
||||
const float trackWidth = std::max(24.0f, sliderWidth - valueWidth - kSliderValueGap * m_scale);
|
||||
const float trackHeight = kTrackHeight * m_scale;
|
||||
const float valueLeft = contentLeft + trackWidth + kSliderValueGap * m_scale;
|
||||
const float resetLeft = contentLeft + contentWidth - resetSize;
|
||||
|
||||
m_widthTrackRect =
|
||||
gd::RectF(contentLeft, widthRow.Y + (rowHeight - trackHeight) * 0.5f, trackWidth, trackHeight);
|
||||
m_widthValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, widthRow.Y,
|
||||
valueWidth, rowHeight);
|
||||
m_widthValueRect = gd::RectF(valueLeft, widthRow.Y, valueWidth, rowHeight);
|
||||
m_widthResetRect =
|
||||
(resetSize > 0.0f)
|
||||
? gd::RectF(resetLeft, widthRow.Y + (rowHeight - resetSize) * 0.5f, resetSize, resetSize)
|
||||
: gd::RectF();
|
||||
|
||||
m_opacityTrackRect = gd::RectF(contentLeft, opacityRow.Y + (rowHeight - trackHeight) * 0.5f,
|
||||
trackWidth, trackHeight);
|
||||
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + kSliderValueGap * m_scale, opacityRow.Y,
|
||||
valueWidth, rowHeight);
|
||||
m_opacityValueRect = gd::RectF(valueLeft, opacityRow.Y, valueWidth, rowHeight);
|
||||
m_opacityResetRect =
|
||||
(resetSize > 0.0f)
|
||||
? gd::RectF(resetLeft, opacityRow.Y + (rowHeight - resetSize) * 0.5f, resetSize, resetSize)
|
||||
: gd::RectF();
|
||||
|
||||
// 「目标」行的子几何依赖目标信号的名称与可见性(可能在未重排时变化),
|
||||
// 因此每次重排后让缓存失效,下一次绘制 / 命中测试时按需重算。
|
||||
|
||||
@@ -65,7 +65,9 @@ namespace jdi
|
||||
if (UiPointInRect(m_adjustRects[kAdjustGain], fx, fy) ||
|
||||
UiPointInRect(m_adjustRects[kAdjustMirror], fx, fy) ||
|
||||
UiPointInRect(m_adjustRects[kAdjustAbsolute], fx, fy) || UiPointInRect(m_widthValueRect, fx, fy) ||
|
||||
UiPointInRect(m_opacityValueRect, fx, fy))
|
||||
UiPointInRect(m_opacityValueRect, fx, fy) ||
|
||||
(m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy)) ||
|
||||
(m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy)))
|
||||
{
|
||||
return LoadCursorW(nullptr, IDC_HAND);
|
||||
}
|
||||
@@ -652,4 +654,51 @@ namespace jdi
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignalPanel::ResetStyleLineWidth()
|
||||
{
|
||||
if (m_dataSet == nullptr || m_dataSetWritable == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const SignalData* signal = m_dataSet->Resolve(SelectedRef());
|
||||
if (signal == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 默认线与新建信号同源(`DefaultSignalLineWidth`):单数据点信号用最大线宽,
|
||||
// 其余用 `kDefaultSignalLineWidth`(用户明确要求"单值信号和普通信号的默认线宽不一样")。
|
||||
const float target = DefaultSignalLineWidth(signal->finiteCount);
|
||||
if (std::fabs(style->lineWidth - target) < 0.001f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
style->lineWidth = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignalPanel::ResetStyleOpacity()
|
||||
{
|
||||
if (m_dataSetWritable == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SignalStyle* style = StyleOf(SelectedRef());
|
||||
if (style == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (std::fabs(style->opacity - 1.0f) < 0.001f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
style->opacity = 1.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jdi
|
||||
|
||||
@@ -195,6 +195,17 @@ namespace jdi
|
||||
{
|
||||
hoverStyle = StyleHit::WIDTH_VALUE;
|
||||
}
|
||||
// 两行右侧的复位按钮(在数值框右边,命中区不重叠,但顺序放在数值之后更安全)。
|
||||
if (hoverStyle == StyleHit::NONE && m_widthResetRect.Width > 0.0f &&
|
||||
UiPointInRect(m_widthResetRect, fx, fy))
|
||||
{
|
||||
hoverStyle = StyleHit::WIDTH_RESET;
|
||||
}
|
||||
if (hoverStyle == StyleHit::NONE && m_opacityResetRect.Width > 0.0f &&
|
||||
UiPointInRect(m_opacityResetRect, fx, fy))
|
||||
{
|
||||
hoverStyle = StyleHit::OPACITY_RESET;
|
||||
}
|
||||
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_adjustRects[kAdjustGain], fx, fy))
|
||||
{
|
||||
hoverStyle = StyleHit::GAIN_ADJUST;
|
||||
@@ -424,6 +435,20 @@ namespace jdi
|
||||
ScrollToSignalAnimated(SelectedRef());
|
||||
return BeginEdit(StyleEditField::OPACITY);
|
||||
}
|
||||
// 两行右侧的**复位按钮**:把该行参数恢复为默认值(线宽按信号类型取默认、
|
||||
// 不透明度回 100%)。与「系数/偏移」还原图标同一交互语义:就地回显、不弹任何东西。
|
||||
if (m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy))
|
||||
{
|
||||
ResetStyleLineWidth();
|
||||
ScrollToSignalAnimated(SelectedRef());
|
||||
return true;
|
||||
}
|
||||
if (m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy))
|
||||
{
|
||||
ResetStyleOpacity();
|
||||
ScrollToSignalAnimated(SelectedRef());
|
||||
return true;
|
||||
}
|
||||
// 「调整」行:系数与镜像(按下只登记,**抬起**时才展开各自的二级面板,
|
||||
// 与自定义颜色入口同一套交互)、绝对值(单击即切换)。
|
||||
if (UiPointInRect(m_adjustRects[kAdjustGain], fx, fy))
|
||||
@@ -821,6 +846,12 @@ namespace jdi
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 线宽 / 透明度两行右侧的复位按钮是"单次点击即生效"的按钮:双击不得被解释成别的语义。
|
||||
if ((m_widthResetRect.Width > 0.0f && UiPointInRect(m_widthResetRect, fx, fy)) ||
|
||||
(m_opacityResetRect.Width > 0.0f && UiPointInRect(m_opacityResetRect, fx, fy)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
UpdateTargetRowGeometry();
|
||||
if (m_targetNameRect.Width > 0.0f && UiPointInRect(m_targetNameRect, fx, fy))
|
||||
{
|
||||
|
||||
@@ -768,6 +768,9 @@ namespace jdi
|
||||
m_theme->textMuted, TextAlign::CENTER);
|
||||
return;
|
||||
}
|
||||
// 目标信号本身:线宽复位按钮需要它的有效采样点数(单点信号默认用最大线宽)。
|
||||
const SignalData* signal =
|
||||
(m_dataSet != nullptr) ? m_dataSet->Resolve(SelectedRef()) : nullptr;
|
||||
|
||||
const float labelWidth = kStyleLabelWidth * m_scale;
|
||||
|
||||
@@ -779,6 +782,27 @@ namespace jdi
|
||||
m_theme->textMuted, TextAlign::LEFT);
|
||||
};
|
||||
|
||||
// 「线宽 / 透明度」两行右侧的**复位按钮**(与「系数/偏移」二级菜单标题栏的还原图标同款)。
|
||||
// `changed` = 当前值是否已偏离默认值:偏离时才用强调色(一眼看出"这里可以点"),
|
||||
// 已是默认值时用弱色(仍可点,但结果不变)。
|
||||
auto paintStyleReset = [&](const gd::RectF& rect, StyleHit hit, bool changed)
|
||||
{
|
||||
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const bool hot = (m_hoverStyle == hit);
|
||||
if (hot)
|
||||
{
|
||||
UiFillRoundRect(g, gd::RectF(rect.X - 1.0f * m_scale, rect.Y - 1.0f * m_scale,
|
||||
rect.Width + 2.0f * m_scale, rect.Height + 2.0f * m_scale),
|
||||
3.0f * m_scale, WithAlpha(m_theme->accent, 0x33));
|
||||
}
|
||||
UiPaintResetIcon(g, rect, hot ? m_theme->accent
|
||||
: (changed ? m_theme->text : m_theme->textMuted),
|
||||
m_scale);
|
||||
};
|
||||
|
||||
// ---- 第一行:颜色(预设色块 + 自定义颜色入口)----
|
||||
{
|
||||
paintRowLabel(0, L"颜色");
|
||||
@@ -888,7 +912,7 @@ namespace jdi
|
||||
paintToggle(kAdjustAbsolute, L"绝对值", style->absolute, StyleHit::ABSOLUTE_TOGGLE);
|
||||
}
|
||||
|
||||
// ---- 第四行:线宽(数值可点击输入)----
|
||||
// ---- 第四行:线宽(数值可点击输入;右侧为复位按钮)----
|
||||
{
|
||||
paintRowLabel(3, L"线宽");
|
||||
|
||||
@@ -913,6 +937,9 @@ namespace jdi
|
||||
: fullText;
|
||||
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong, TextAlign::RIGHT);
|
||||
}
|
||||
paintStyleReset(m_widthResetRect, StyleHit::WIDTH_RESET,
|
||||
style->lineWidth !=
|
||||
DefaultSignalLineWidth(signal != nullptr ? signal->finiteCount : 0));
|
||||
}
|
||||
|
||||
// ---- 第五行:透明度(数值可点击输入)----
|
||||
@@ -933,6 +960,8 @@ namespace jdi
|
||||
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
|
||||
m_theme->textStrong, TextAlign::RIGHT);
|
||||
}
|
||||
paintStyleReset(m_opacityResetRect, StyleHit::OPACITY_RESET,
|
||||
std::fabs(style->opacity - 1.0f) > 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user