修复合并网格后的新增信号、适应视图联动、调色面板排版与左右越界三角

1. 合并网格后新增信号不再挤进合并网格
   - 分组复选框与「全部显示」按钮此前只更新可见性,没有像单信号勾选那样把划分策略
     切到自定义,于是合并网格把新增信号一并吞掉。
   - 现在两者在确有信号由不可见变可见时同样切换到自定义划分,
     新增信号各自独占一个网格并追加在列表最下方,原合并网格保持不变。

2. 适应视图不再取消「选区放大」模式与「选区 Y 轴自适应」
   - 此前适应视图会把缩放模式复位为无,并把全部网格的 autoRange 置真;
     而自动适应区间只由 autoRange 决定,置真等于把已经算好的时间段幅值丢掉,
     表现为"点一下适应视图,Y 轴自适应就失效了"。
   - 现在保留缩放模式;开启了「选区 Y 轴自适应」的网格改为按新的时间视图重新匹配,
     其余网格才恢复整段自适应。

3. 「按网格时间轴展开」联动其它网格
   - 时间视图是全局的,展开后其余开启了「选区 Y 轴自适应」的网格同样重新匹配 Y 轴,
     不再只有被展开的那一个网格量程合适。

4. 自定义颜色面板排版重做
   - R/G/B 的标签、滑块与数值改为同属右侧一列并共用同一行,标签紧贴各自的滑块;
     预览色块与色号移到左列下方并与色域方块同宽,色号编辑框不再超出面板边界;
     面板高度按内容重新计算,底部不再留空。
   - 色域方块上的选点去掉外圈深色描边,改为"中心为所选颜色 + 白色描边"。
   - 色相指示器由黑色横条改为白色描边的圆角长方条,底色取该位置的颜色;
     色相条本身改用分段线性渐变绘制。

5. 修复光标数据点向左/向右越界时的三角
   - 三角沿朝向伸出的深度大于贴边时距边界的距离,导致有一大半画在网格绘制区之外,
     裁剪后只剩残缺一角、描边也因此不完整。
   - 现在左右越界时把锚点向内推一个三角深度,三角完整落在绘制区内;
     同时四方向越界都让三角与显示的数值纵向居中对齐。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-10 14:38:54 +08:00
1 parent 40b12d6e8b
commit 2d029fb09b
3 files changed
+152 -68

No files matched your search

+79 -55
View File
@@ -29,6 +29,8 @@ namespace jdi
const float kHueWidth = 14.0f; /**< 色相条宽度。 */
const float kGamutGap = 10.0f; /**< 色域方块与色相条间距。 */
const float kColumnGap = 12.0f; /**< 色相条与滑块列间距。 */
const float kSwatchWidth = 52.0f; /**< 预览色块宽度。 */
const float kSwatchHexGap = 8.0f; /**< 预览色块与色号间距。 */
const float kGamutBorder = 1.0f; /**< 色域方块/色相条描边宽度。 */
/** @brief 通道显示名称。 */
@@ -216,12 +218,13 @@ namespace jdi
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
{
// 布局:左侧一列放"色域方块 + 色相条",右侧一列放 R/G/B 三行
//(每行 = 通道标签 + 滑块 + 数值),两列下方再放"预览色块 + 色号"一行。
// 所有控件都从面板内边距起算,因此不会出现越界。
const float width = kPanelWidth * m_scale;
// 高度:标题 + 内容区。内容区里"色域方块"是最高的控件,
// 预览行/色号行落在它与滑块列的下方,仍在其纵向范围内,不再额外叠加高度。
const float contentHeight = std::max(kGamutSize, kRowHeight * 3.0f + kRowGap * 2.0f +
kPreviewGap + kPreviewHeight);
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + contentHeight) * m_scale;
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + kGamutSize + kPreviewGap +
kPreviewHeight) *
m_scale;
const float gap = 4.0f * m_scale;
@@ -247,38 +250,46 @@ namespace jdi
width - kPadH * 2.0f * m_scale, kTitleHeight * m_scale);
const float contentTop = m_titleRect.Y + m_titleRect.Height + kTitleGap * m_scale;
const float contentLeft = left + kPadH * m_scale;
const float contentRight = left + width - kPadH * m_scale;
// 左侧:色域方块(横轴饱和度、纵轴明度)+ 色相条。
m_gamutRect = gd::RectF(left + kPadH * m_scale, contentTop, kGamutSize * m_scale,
kGamutSize * m_scale);
// 左列:色域方块(横轴饱和度、纵轴明度)+ 色相条。
m_gamutRect = gd::RectF(contentLeft, contentTop, kGamutSize * m_scale, kGamutSize * m_scale);
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
kHueWidth * m_scale, kGamutSize * m_scale);
// 右侧:R/G/B 滑块 + 数值,紧随色相条之后。
const float columnLeft = m_hueRect.X + m_hueRect.Width + kColumnGap * m_scale;
const float columnWidth =
std::max(80.0f * m_scale, m_rect.X + m_rect.Width - kPadH * m_scale - columnLeft);
// 右列:R/G/B 三行。整列在方块高度内纵向居中,与左侧方块视觉平衡。
const float leftColumnWidth =
(kGamutSize + kGamutGap + kHueWidth) * m_scale;
const float columnLeft = m_gamutRect.X + leftColumnWidth + kColumnGap * m_scale;
const float columnWidth = std::max(60.0f * m_scale, contentRight - columnLeft);
const float rowsHeight = (kRowHeight * 3.0f + kRowGap * 2.0f) * m_scale;
const float firstRowTop =
contentTop + std::max(0.0f, (kGamutSize * m_scale - rowsHeight) * 0.5f);
const float trackLeft = columnLeft + (kLabelWidth + kLabelGap) * m_scale;
const float trackWidth =
std::max(24.0f, columnWidth - (kLabelWidth + kLabelGap + kValueWidth) * m_scale);
std::max(24.0f, columnWidth - (kLabelWidth + kLabelGap + kValueWidth + 6.0f) * m_scale);
for (int index = 0; index < 3; ++index)
{
const float rowTop =
contentTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
const float rowTop = firstRowTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
m_tracks[static_cast<std::size_t>(index)] =
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
trackWidth, kTrackHeight * m_scale);
m_values[static_cast<std::size_t>(index)] =
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop,
kValueWidth * m_scale - 6.0f * m_scale, kRowHeight * m_scale);
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop, kValueWidth * m_scale,
kRowHeight * m_scale);
}
const float previewTop =
contentTop + (3.0f * (kRowHeight + kRowGap) + kPreviewGap) * m_scale;
m_previewRect = gd::RectF(trackLeft, previewTop + 2.0f * m_scale, 52.0f * m_scale, 20.0f * m_scale);
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + 8.0f * m_scale,
previewTop + 2.0f * m_scale, 96.0f * m_scale, 20.0f * m_scale);
// 下行:预览色块 + 色号,与上方的"方块 + 色相条"同宽。
const float previewTop = contentTop + (kGamutSize + kPreviewGap) * m_scale;
m_previewRect =
gd::RectF(contentLeft, previewTop, kSwatchWidth * m_scale, kPreviewHeight * m_scale);
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + kSwatchHexGap * m_scale,
previewTop,
std::max(40.0f * m_scale, leftColumnWidth - (kSwatchWidth + kSwatchHexGap) * m_scale),
kPreviewHeight * m_scale);
}
void ColorPickerPopup::Open(const gd::RectF& bounds, const gd::RectF& anchor, std::uint32_t color)
@@ -911,43 +922,55 @@ namespace jdi
}
UiDrawRoundRect(g, m_gamutRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
// ---- 色相条(纵向 0-1,用 6 段线性渐变近似色相环)----
// ---- 色相条(纵向 0-1):6 段线性渐变拼出完整色相环 ----
{
const Hsv hsv = ToHsv(m_channels);
const double saturation = (hsv.s > 1.0e-4) ? hsv.s : 1.0;
const double value = (hsv.v > 1.0e-4) ? hsv.v : 1.0;
const int bands = std::max(12, static_cast<int>(std::lround(m_hueRect.Height)));
for (int index = 0; index < bands; ++index)
for (int segment = 0; segment < 6; ++segment)
{
const double hue = (static_cast<double>(index) + 0.5) / static_cast<double>(bands);
const std::array<double, 3> rgb = FromHsv(hue, 1.0, 1.0);
const std::uint32_t band = 0xFF000000u | (ToByte(rgb[0]) << 16) | (ToByte(rgb[1]) << 8) |
ToByte(rgb[2]);
const float top = m_hueRect.Y + m_hueRect.Height * index / static_cast<float>(bands);
const float bottom =
m_hueRect.Y + m_hueRect.Height * (index + 1) / static_cast<float>(bands);
UiFillRect(g, gd::RectF(m_hueRect.X, top, m_hueRect.Width, bottom - top), band);
const double hue0 = static_cast<double>(segment) / 6.0;
const double hue1 = static_cast<double>(segment + 1) / 6.0;
const std::array<double, 3> rgb0 = FromHsv(hue0, 1.0, 1.0);
const std::array<double, 3> rgb1 = FromHsv(hue1, 1.0, 1.0);
const std::uint32_t color0 =
0xFF000000u | (ToByte(rgb0[0]) << 16) | (ToByte(rgb0[1]) << 8) | ToByte(rgb0[2]);
const std::uint32_t color1 =
0xFF000000u | (ToByte(rgb1[0]) << 16) | (ToByte(rgb1[1]) << 8) | ToByte(rgb1[2]);
const float top = m_hueRect.Y + m_hueRect.Height * static_cast<float>(hue0);
const float bottom = m_hueRect.Y + m_hueRect.Height * static_cast<float>(hue1);
const gd::Point from(static_cast<INT>(std::lround(m_hueRect.X)),
static_cast<INT>(std::lround(top)));
const gd::Point to(static_cast<INT>(std::lround(m_hueRect.X)),
static_cast<INT>(std::lround(bottom)));
gd::LinearGradientBrush brush(from, to, ToGdiColor(color0), ToGdiColor(color1));
// 渐变端点会各自向两侧延伸半段,逐段微重叠地填充即可无缝拼接。
g.FillRectangle(&brush, m_hueRect.X, top, m_hueRect.Width, bottom - top);
}
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->border, kGamutBorder);
// 控件指示器:色域方块上标出 (S, V),色相条上标出当前色相。
// 两者都由 m_channels 反推,因此与 R/G/B 滑块、十六进制输入天然联动。
const float markerX = m_gamutRect.X + static_cast<float>(saturation) * m_gamutRect.Width;
const float markerY = m_gamutRect.Y + static_cast<float>(1.0 - value) * m_gamutRect.Height;
gd::Pen outerPen(ToGdiColor(0xFF1B1B1Bu), 2.0f * m_scale);
gd::Pen innerPen(ToGdiColor(0xFFFFFFFFu), 1.4f * m_scale);
const float outerRadius = 5.0f * m_scale;
g.DrawEllipse(&outerPen, markerX - outerRadius, markerY - outerRadius, outerRadius * 2.0f,
outerRadius * 2.0f);
const float innerRadius = 3.4f * m_scale;
g.DrawEllipse(&innerPen, markerX - innerRadius, markerY - innerRadius, innerRadius * 2.0f,
innerRadius * 2.0f);
// ---- 色域方块上的选点:中心就是当前选中的颜色,外面只加一圈白色描边 ----
const Hsv hsv = ToHsv(m_channels);
const float markerX = m_gamutRect.X + static_cast<float>(hsv.s) * m_gamutRect.Width;
const float markerY = m_gamutRect.Y + static_cast<float>(1.0 - hsv.v) * m_gamutRect.Height;
const float markerRadius = 5.5f * m_scale;
gd::SolidBrush markerBrush(ToGdiColor(CurrentColor()));
g.FillEllipse(&markerBrush, markerX - markerRadius, markerY - markerRadius,
markerRadius * 2.0f, markerRadius * 2.0f);
gd::Pen markerPen(ToGdiColor(0xFFFFFFFFu), 2.0f * m_scale);
g.DrawEllipse(&markerPen, markerX - markerRadius, markerY - markerRadius,
markerRadius * 2.0f, markerRadius * 2.0f);
const float hueMarkerY = m_hueRect.Y + static_cast<float>(CurrentHue()) * m_hueRect.Height;
UiFillRect(g, gd::RectF(m_hueRect.X - 2.0f * m_scale, hueMarkerY - 1.5f * m_scale,
m_hueRect.Width + 4.0f * m_scale, 3.0f * m_scale),
0xFF1B1B1Bu);
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
// ---- 色相条上的指示器:白色描边的圆角长方条,底色就是该位置的颜色 ----
const double hue = CurrentHue();
const std::array<double, 3> hueRgb = FromHsv(hue, 1.0, 1.0);
const std::uint32_t hueColor =
0xFF000000u | (ToByte(hueRgb[0]) << 16) | (ToByte(hueRgb[1]) << 8) | ToByte(hueRgb[2]);
const float hueMarkerY = m_hueRect.Y + static_cast<float>(hue) * m_hueRect.Height;
const float pillHeight = 10.0f * m_scale;
const float pillWidth = m_hueRect.Width + 9.0f * m_scale;
const gd::RectF pill(m_hueRect.X + (m_hueRect.Width - pillWidth) * 0.5f,
hueMarkerY - pillHeight * 0.5f, pillWidth, pillHeight);
UiFillRoundRect(g, pill, pillHeight * 0.5f, hueColor);
UiDrawRoundRect(g, pill, pillHeight * 0.5f, 0xFFFFFFFFu, 1.8f * m_scale);
}
for (int index = 0; index < 3; ++index)
@@ -955,7 +978,8 @@ namespace jdi
const std::size_t slot = static_cast<std::size_t>(index);
const gd::RectF& track = m_tracks[slot];
const gd::RectF labelRect(m_rect.X + kPadH * m_scale, track.Y - 9.0f * m_scale,
// 通道标签放在滑块同一行的左侧,与轨道/数值纵向对齐。
const gd::RectF labelRect(track.X - (kLabelWidth + kLabelGap) * m_scale, m_values[slot].Y,
kLabelWidth * m_scale, kRowHeight * m_scale);
UiText(g, kChannelNames[index], m_fonts->uiSmall, labelRect, m_theme->textMuted, TextAlign::LEFT);
+23
View File
@@ -1252,12 +1252,35 @@ namespace jdi
return;
}
std::vector<bool>& fileVisible = m_visible[static_cast<std::size_t>(fileIndex)];
// 合并模式下“新增”的信号同样应各自独占网格(与 SetVisible 一致):
// 先切到自定义划分,RebuildLanes 会把尚未分配网格的可见信号追加到最下方。
// 只有确实存在“原本不可见”的信号时才切模式,避免重复点击默默改掉划分策略。
const bool anyHidden =
std::find(fileVisible.begin(), fileVisible.end(), false) != fileVisible.end();
if (visible && anyHidden && m_laneMode == LaneMode::MERGED)
{
m_laneMode = LaneMode::CUSTOM;
}
std::fill(fileVisible.begin(), fileVisible.end(), visible);
SyncLanes();
}
void TabConfig::SetAllVisible(bool visible)
{
bool anyHidden = false;
for (const std::vector<bool>& fileVisible : m_visible)
{
if (std::find(fileVisible.begin(), fileVisible.end(), false) != fileVisible.end())
{
anyHidden = true;
break;
}
}
// 同上:合并模式下新增的信号各自独占网格,且排列在最下方。
if (visible && anyHidden && m_laneMode == LaneMode::MERGED)
{
m_laneMode = LaneMode::CUSTOM;
}
for (std::vector<bool>& fileVisible : m_visible)
{
std::fill(fileVisible.begin(), fileVisible.end(), visible);