修复合并网格后的新增信号、适应视图联动、调色面板排版与左右越界三角
1. 合并网格后新增信号不再挤进合并网格
- 分组复选框与「全部显示」按钮此前只更新可见性,没有像单信号勾选那样把划分策略
切到自定义,于是合并网格把新增信号一并吞掉。
- 现在两者在确有信号由不可见变可见时同样切换到自定义划分,
新增信号各自独占一个网格并追加在列表最下方,原合并网格保持不变。
2. 适应视图不再取消「选区放大」模式与「选区 Y 轴自适应」
- 此前适应视图会把缩放模式复位为无,并把全部网格的 autoRange 置真;
而自动适应区间只由 autoRange 决定,置真等于把已经算好的时间段幅值丢掉,
表现为"点一下适应视图,Y 轴自适应就失效了"。
- 现在保留缩放模式;开启了「选区 Y 轴自适应」的网格改为按新的时间视图重新匹配,
其余网格才恢复整段自适应。
3. 「按网格时间轴展开」联动其它网格
- 时间视图是全局的,展开后其余开启了「选区 Y 轴自适应」的网格同样重新匹配 Y 轴,
不再只有被展开的那一个网格量程合适。
4. 自定义颜色面板排版重做
- R/G/B 的标签、滑块与数值改为同属右侧一列并共用同一行,标签紧贴各自的滑块;
预览色块与色号移到左列下方并与色域方块同宽,色号编辑框不再超出面板边界;
面板高度按内容重新计算,底部不再留空。
- 色域方块上的选点去掉外圈深色描边,改为"中心为所选颜色 + 白色描边"。
- 色相指示器由黑色横条改为白色描边的圆角长方条,底色取该位置的颜色;
色相条本身改用分段线性渐变绘制。
5. 修复光标数据点向左/向右越界时的三角
- 三角沿朝向伸出的深度大于贴边时距边界的距离,导致有一大半画在网格绘制区之外,
裁剪后只剩残缺一角、描边也因此不完整。
- 现在左右越界时把锚点向内推一个三角深度,三角完整落在绘制区内;
同时四方向越界都让三角与显示的数值纵向居中对齐。
This commit is contained in:
1 parent
40b12d6e8b
commit
2d029fb09b
3 files changed
+152
-68
No files matched your search
@@ -31,6 +31,9 @@ namespace jdi
|
|||||||
const float kCursorHandleReserve = 9.0f; /**< 光标顶部三角把手占用的高度(数据点贴边时需避让)。 */
|
const float kCursorHandleReserve = 9.0f; /**< 光标顶部三角把手占用的高度(数据点贴边时需避让)。 */
|
||||||
const float kMarkerEdgeInset = 7.0f; /**< 数据点越界贴边时距网格可见边界的间距。 */
|
const float kMarkerEdgeInset = 7.0f; /**< 数据点越界贴边时距网格可见边界的间距。 */
|
||||||
const float kMarkerInsideMargin = 2.0f; /**< 数据点在视界内时允许贴到边界的最小余量。 */
|
const float kMarkerInsideMargin = 2.0f; /**< 数据点在视界内时允许贴到边界的最小余量。 */
|
||||||
|
const float kMarkerTriangleArm = 5.0f; /**< 越界方向三角"半宽/半高"(垂直于朝向的半边长)。 */
|
||||||
|
/** 越界方向三角沿朝向的深度(与光标把手同比例)。 */
|
||||||
|
const float kMarkerTriangleDepth = kMarkerTriangleArm * 1.4f;
|
||||||
const size_t kMaxScatterPoints = 2500; /**< 单条散点最大绘制点数。 */
|
const size_t kMaxScatterPoints = 2500; /**< 单条散点最大绘制点数。 */
|
||||||
const size_t kPolylineCacheLimit = 64; /**< 折线顶点缓存条目上限。 */
|
const size_t kPolylineCacheLimit = 64; /**< 折线顶点缓存条目上限。 */
|
||||||
const float kMinScatterRadius = 1.6f; /**< 散点最小半径(逻辑像素)。 */
|
const float kMinScatterRadius = 1.6f; /**< 散点最小半径(逻辑像素)。 */
|
||||||
@@ -883,21 +886,35 @@ namespace jdi
|
|||||||
Tab().ViewMax() = high;
|
Tab().ViewMax() = high;
|
||||||
Tab().ViewInitialized() = true;
|
Tab().ViewInitialized() = true;
|
||||||
Tab().LaneScroll() = 0.0f;
|
Tab().LaneScroll() = 0.0f;
|
||||||
// 自适应即回到初始状态:退出拖拽缩放模式并放下进行中的选区与拖拽。
|
// 只放下进行中的选区与拖拽,**不改变「选区放大」等缩放模式的启用状态**
|
||||||
Tab().ZoomMode() = PlotZoomMode::NONE;
|
// (用户明确要求:点适应视图后仍保持已启用的缩放模式)。
|
||||||
m_rubberRect = gd::RectF();
|
m_rubberRect = gd::RectF();
|
||||||
m_dragMode = PlotDragMode::NONE;
|
m_dragMode = PlotDragMode::NONE;
|
||||||
m_dragSignal = SignalRef();
|
m_dragSignal = SignalRef();
|
||||||
m_dragTargetLane = -1;
|
m_dragTargetLane = -1;
|
||||||
m_hoverSignal = SignalRef();
|
m_hoverSignal = SignalRef();
|
||||||
|
SetHint(Tab().ZoomMode() == PlotZoomMode::NONE
|
||||||
|
? std::wstring()
|
||||||
|
: L"横向或纵向拖动以放大选择区域,ESC退出选区模式");
|
||||||
|
|
||||||
if (m_tab != nullptr)
|
if (m_tab != nullptr)
|
||||||
{
|
{
|
||||||
// 适应视图 = 回到初始状态:全部网格恢复整段数据的自动自适应。
|
// 适应视图 = 回到初始状态:恢复整段数据的自动自适应。
|
||||||
// 「选区 Y 轴自适应」是用户设置项,这里保持不变。
|
// 但**开启了「选区 Y 轴自适应」的网格必须继续跟随时间视图**:
|
||||||
for (Lane& lane : m_tab->Lanes())
|
// 直接置 autoRange 会让 ComputeLaneRange 忽略已经算好的时间段幅值,
|
||||||
|
// 表现为"点一下适应视图就把 Y 轴自适应关掉了"。这里改为按新的
|
||||||
|
// (整段)时间视图重新匹配一次,开关状态与视觉效果都保持。
|
||||||
|
const int laneCount = static_cast<int>(m_tab->Lanes().size());
|
||||||
|
for (int index = 0; index < laneCount; ++index)
|
||||||
{
|
{
|
||||||
lane.autoRange = true;
|
if (m_tab->Lanes()[static_cast<std::size_t>(index)].fitYToSelection)
|
||||||
|
{
|
||||||
|
AutoRangeLaneToView(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_tab->Lanes()[static_cast<std::size_t>(index)].autoRange = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 适应视图已回到初始状态,这里不再移动光标(光标允许停留在可视区外)。
|
// 适应视图已回到初始状态,这里不再移动光标(光标允许停留在可视区外)。
|
||||||
@@ -1152,6 +1169,10 @@ namespace jdi
|
|||||||
// 否则展开后会看到“时间对了但幅值区间不合适”的波形。
|
// 否则展开后会看到“时间对了但幅值区间不合适”的波形。
|
||||||
AutoRangeLaneToView(laneIndex);
|
AutoRangeLaneToView(laneIndex);
|
||||||
|
|
||||||
|
// 其它开启了「选区 Y 轴自适应」的网格同样要跟新的时间视图重新匹配:
|
||||||
|
// 时间视图变了,只让被展开的那个网格合适、其余网格停留在旧幅值区间并不符合预期。
|
||||||
|
ApplySelectionAutoFit();
|
||||||
|
|
||||||
// 注意:不在这里移动光标——光标允许滞留在网格可视区域之外。
|
// 注意:不在这里移动光标——光标允许滞留在网格可视区域之外。
|
||||||
BuildLaneVisuals();
|
BuildLaneVisuals();
|
||||||
return true;
|
return true;
|
||||||
@@ -5787,9 +5808,10 @@ namespace jdi
|
|||||||
// 依次向下(再向上)避让已占用的标注,保证不会互相覆盖。
|
// 依次向下(再向上)避让已占用的标注,保证不会互相覆盖。
|
||||||
PlaceCursorLabel(labelRect, labelTop, textHeight, clipTop, clipBottom);
|
PlaceCursorLabel(labelRect, labelTop, textHeight, clipTop, clipBottom);
|
||||||
|
|
||||||
// 上下越界的方向三角与最终落位的取值标注纵向居中对齐
|
// 越界方向三角与最终落位的取值标注纵向居中对齐
|
||||||
// (用户明确要求“超过上下视界时三角需要与显示的数值居中对齐”)。
|
// (用户明确要求“超过上下视界时三角需要与显示的数值居中对齐”;
|
||||||
if (marker.offUp || marker.offDown)
|
// 左右越界时同样对齐,避免三角与数值错行、看起来不协调)。
|
||||||
|
if (marker.offUp || marker.offDown || marker.offLeft || marker.offRight)
|
||||||
{
|
{
|
||||||
marker.y = labelRect.Y + labelRect.Height * 0.5f;
|
marker.y = labelRect.Y + labelRect.Height * 0.5f;
|
||||||
}
|
}
|
||||||
@@ -5865,8 +5887,9 @@ namespace jdi
|
|||||||
std::min(centerTop, clipBottom - textHeight - 1.0f * m_scale));
|
std::min(centerTop, clipBottom - textHeight - 1.0f * m_scale));
|
||||||
gd::RectF centerRect(centerLeft, centerTop, centerTextWidth, textHeight);
|
gd::RectF centerRect(centerLeft, centerTop, centerTextWidth, textHeight);
|
||||||
PlaceCursorLabel(centerRect, centerTop, textHeight, clipTop, clipBottom);
|
PlaceCursorLabel(centerRect, centerTop, textHeight, clipTop, clipBottom);
|
||||||
// 与原始信号同一规则:上下越界的方向三角与取值标注纵向居中对齐。
|
// 与原始信号同一规则:越界方向三角与取值标注纵向居中对齐。
|
||||||
if (centerMarker.offUp || centerMarker.offDown)
|
if (centerMarker.offUp || centerMarker.offDown || centerMarker.offLeft ||
|
||||||
|
centerMarker.offRight)
|
||||||
{
|
{
|
||||||
centerMarker.y = centerRect.Y + centerRect.Height * 0.5f;
|
centerMarker.y = centerRect.Y + centerRect.Height * 0.5f;
|
||||||
}
|
}
|
||||||
@@ -6084,6 +6107,20 @@ namespace jdi
|
|||||||
marker.offDown = (rawY > clipBottom);
|
marker.offDown = (rawY > clipBottom);
|
||||||
|
|
||||||
marker.x = pinX;
|
marker.x = pinX;
|
||||||
|
// 左右越界时三角沿水平方向**向外**伸出 `kMarkerTriangleDepth`:
|
||||||
|
// 光标贴边时 pinX 只离边界 1px,若直接把锚点放在那里,三角会有一大半画到
|
||||||
|
// 网格绘制区之外(被裁剪后只看到残缺的一角,描边也随之断开)。
|
||||||
|
// 因此把锚点沿水平方向**向内**推 keepIn,保证三角完整落在绘制区内。
|
||||||
|
const float keepIn = (kMarkerTriangleDepth + 1.0f) * m_scale;
|
||||||
|
if (marker.offLeft)
|
||||||
|
{
|
||||||
|
marker.x = std::max(pinX, left + keepIn);
|
||||||
|
}
|
||||||
|
else if (marker.offRight)
|
||||||
|
{
|
||||||
|
marker.x = std::min(pinX, right - keepIn);
|
||||||
|
}
|
||||||
|
|
||||||
if (marker.offUp)
|
if (marker.offUp)
|
||||||
{
|
{
|
||||||
// 上越界:为方向三角与顶部把手预留高度,避免压住把手。
|
// 上越界:为方向三角与顶部把手预留高度,避免压住把手。
|
||||||
@@ -6108,8 +6145,8 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
gd::SolidBrush ring(ToGdiColor(ringColor));
|
gd::SolidBrush ring(ToGdiColor(ringColor));
|
||||||
gd::SolidBrush fill(ToGdiColor(fillColor));
|
gd::SolidBrush fill(ToGdiColor(fillColor));
|
||||||
const float arm = 5.0f * m_scale; /**< 三角“半宽/半高”(垂直于朝向的半边长)。 */
|
const float arm = kMarkerTriangleArm * m_scale; /**< 三角“半宽/半高”(垂直于朝向的半边长)。 */
|
||||||
const float depth = arm * 1.4f; /**< 三角沿朝向的深度(与光标把手同比例)。 */
|
const float depth = kMarkerTriangleDepth * m_scale; /**< 三角沿朝向的深度(与光标把手同比例)。 */
|
||||||
|
|
||||||
// 越界三角:**尖端朝外**(指向数据点所在的方向,即视界之外),底边在网格内侧;
|
// 越界三角:**尖端朝外**(指向数据点所在的方向,即视界之外),底边在网格内侧;
|
||||||
// 三角外接矩形在横/纵两个方向都与锚点**居中**(上下越界时因此与取值标注纵向居中)。
|
// 三角外接矩形在横/纵两个方向都与锚点**居中**(上下越界时因此与取值标注纵向居中)。
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ namespace jdi
|
|||||||
const float kHueWidth = 14.0f; /**< 色相条宽度。 */
|
const float kHueWidth = 14.0f; /**< 色相条宽度。 */
|
||||||
const float kGamutGap = 10.0f; /**< 色域方块与色相条间距。 */
|
const float kGamutGap = 10.0f; /**< 色域方块与色相条间距。 */
|
||||||
const float kColumnGap = 12.0f; /**< 色相条与滑块列间距。 */
|
const float kColumnGap = 12.0f; /**< 色相条与滑块列间距。 */
|
||||||
|
const float kSwatchWidth = 52.0f; /**< 预览色块宽度。 */
|
||||||
|
const float kSwatchHexGap = 8.0f; /**< 预览色块与色号间距。 */
|
||||||
const float kGamutBorder = 1.0f; /**< 色域方块/色相条描边宽度。 */
|
const float kGamutBorder = 1.0f; /**< 色域方块/色相条描边宽度。 */
|
||||||
|
|
||||||
/** @brief 通道显示名称。 */
|
/** @brief 通道显示名称。 */
|
||||||
@@ -216,12 +218,13 @@ namespace jdi
|
|||||||
|
|
||||||
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
|
void ColorPickerPopup::BuildLayout(const gd::RectF& bounds, const gd::RectF& anchor)
|
||||||
{
|
{
|
||||||
|
// 布局:左侧一列放"色域方块 + 色相条",右侧一列放 R/G/B 三行
|
||||||
|
//(每行 = 通道标签 + 滑块 + 数值),两列下方再放"预览色块 + 色号"一行。
|
||||||
|
// 所有控件都从面板内边距起算,因此不会出现越界。
|
||||||
const float width = kPanelWidth * m_scale;
|
const float width = kPanelWidth * m_scale;
|
||||||
// 高度:标题 + 内容区。内容区里"色域方块"是最高的控件,
|
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + kGamutSize + kPreviewGap +
|
||||||
// 预览行/色号行落在它与滑块列的下方,仍在其纵向范围内,不再额外叠加高度。
|
kPreviewHeight) *
|
||||||
const float contentHeight = std::max(kGamutSize, kRowHeight * 3.0f + kRowGap * 2.0f +
|
m_scale;
|
||||||
kPreviewGap + kPreviewHeight);
|
|
||||||
const float height = (kPadV * 2.0f + kTitleHeight + kTitleGap + contentHeight) * m_scale;
|
|
||||||
|
|
||||||
const float gap = 4.0f * m_scale;
|
const float gap = 4.0f * m_scale;
|
||||||
|
|
||||||
@@ -247,38 +250,46 @@ namespace jdi
|
|||||||
width - kPadH * 2.0f * m_scale, kTitleHeight * m_scale);
|
width - kPadH * 2.0f * m_scale, kTitleHeight * m_scale);
|
||||||
|
|
||||||
const float contentTop = m_titleRect.Y + m_titleRect.Height + kTitleGap * 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,
|
m_gamutRect = gd::RectF(contentLeft, contentTop, kGamutSize * m_scale, kGamutSize * m_scale);
|
||||||
kGamutSize * m_scale);
|
|
||||||
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
|
m_hueRect = gd::RectF(m_gamutRect.X + m_gamutRect.Width + kGamutGap * m_scale, contentTop,
|
||||||
kHueWidth * m_scale, kGamutSize * m_scale);
|
kHueWidth * m_scale, kGamutSize * m_scale);
|
||||||
|
|
||||||
// 右侧:R/G/B 滑块 + 数值,紧随色相条之后。
|
// 右列:R/G/B 三行。整列在方块高度内纵向居中,与左侧方块视觉平衡。
|
||||||
const float columnLeft = m_hueRect.X + m_hueRect.Width + kColumnGap * m_scale;
|
const float leftColumnWidth =
|
||||||
const float columnWidth =
|
(kGamutSize + kGamutGap + kHueWidth) * m_scale;
|
||||||
std::max(80.0f * m_scale, m_rect.X + m_rect.Width - kPadH * m_scale - columnLeft);
|
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 trackLeft = columnLeft + (kLabelWidth + kLabelGap) * m_scale;
|
||||||
const float trackWidth =
|
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)
|
for (int index = 0; index < 3; ++index)
|
||||||
{
|
{
|
||||||
const float rowTop =
|
const float rowTop = firstRowTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
||||||
contentTop + static_cast<float>(index) * (kRowHeight + kRowGap) * m_scale;
|
|
||||||
m_tracks[static_cast<std::size_t>(index)] =
|
m_tracks[static_cast<std::size_t>(index)] =
|
||||||
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
gd::RectF(trackLeft, rowTop + (kRowHeight * m_scale - kTrackHeight * m_scale) * 0.5f,
|
||||||
trackWidth, kTrackHeight * m_scale);
|
trackWidth, kTrackHeight * m_scale);
|
||||||
m_values[static_cast<std::size_t>(index)] =
|
m_values[static_cast<std::size_t>(index)] =
|
||||||
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop,
|
gd::RectF(trackLeft + trackWidth + 6.0f * m_scale, rowTop, kValueWidth * m_scale,
|
||||||
kValueWidth * m_scale - 6.0f * m_scale, kRowHeight * m_scale);
|
kRowHeight * m_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float previewTop =
|
// 下行:预览色块 + 色号,与上方的"方块 + 色相条"同宽。
|
||||||
contentTop + (3.0f * (kRowHeight + kRowGap) + kPreviewGap) * m_scale;
|
const float previewTop = contentTop + (kGamutSize + kPreviewGap) * m_scale;
|
||||||
m_previewRect = gd::RectF(trackLeft, previewTop + 2.0f * m_scale, 52.0f * m_scale, 20.0f * m_scale);
|
m_previewRect =
|
||||||
m_hexRect = gd::RectF(m_previewRect.X + m_previewRect.Width + 8.0f * m_scale,
|
gd::RectF(contentLeft, previewTop, kSwatchWidth * m_scale, kPreviewHeight * m_scale);
|
||||||
previewTop + 2.0f * m_scale, 96.0f * m_scale, 20.0f * 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)
|
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);
|
UiDrawRoundRect(g, m_gamutRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
|
||||||
|
|
||||||
// ---- 色相条(纵向 0-1,用 6 段线性渐变近似色相环)----
|
// ---- 色相条(纵向 0-1):6 段线性渐变拼出完整色相环 ----
|
||||||
{
|
{
|
||||||
const Hsv hsv = ToHsv(m_channels);
|
for (int segment = 0; segment < 6; ++segment)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
const double hue = (static_cast<double>(index) + 0.5) / static_cast<double>(bands);
|
const double hue0 = static_cast<double>(segment) / 6.0;
|
||||||
const std::array<double, 3> rgb = FromHsv(hue, 1.0, 1.0);
|
const double hue1 = static_cast<double>(segment + 1) / 6.0;
|
||||||
const std::uint32_t band = 0xFF000000u | (ToByte(rgb[0]) << 16) | (ToByte(rgb[1]) << 8) |
|
const std::array<double, 3> rgb0 = FromHsv(hue0, 1.0, 1.0);
|
||||||
ToByte(rgb[2]);
|
const std::array<double, 3> rgb1 = FromHsv(hue1, 1.0, 1.0);
|
||||||
const float top = m_hueRect.Y + m_hueRect.Height * index / static_cast<float>(bands);
|
const std::uint32_t color0 =
|
||||||
const float bottom =
|
0xFF000000u | (ToByte(rgb0[0]) << 16) | (ToByte(rgb0[1]) << 8) | ToByte(rgb0[2]);
|
||||||
m_hueRect.Y + m_hueRect.Height * (index + 1) / static_cast<float>(bands);
|
const std::uint32_t color1 =
|
||||||
UiFillRect(g, gd::RectF(m_hueRect.X, top, m_hueRect.Width, bottom - top), band);
|
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 Hsv hsv = ToHsv(m_channels);
|
||||||
const float markerX = m_gamutRect.X + static_cast<float>(saturation) * m_gamutRect.Width;
|
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 - value) * m_gamutRect.Height;
|
const float markerY = m_gamutRect.Y + static_cast<float>(1.0 - hsv.v) * m_gamutRect.Height;
|
||||||
gd::Pen outerPen(ToGdiColor(0xFF1B1B1Bu), 2.0f * m_scale);
|
const float markerRadius = 5.5f * m_scale;
|
||||||
gd::Pen innerPen(ToGdiColor(0xFFFFFFFFu), 1.4f * m_scale);
|
gd::SolidBrush markerBrush(ToGdiColor(CurrentColor()));
|
||||||
const float outerRadius = 5.0f * m_scale;
|
g.FillEllipse(&markerBrush, markerX - markerRadius, markerY - markerRadius,
|
||||||
g.DrawEllipse(&outerPen, markerX - outerRadius, markerY - outerRadius, outerRadius * 2.0f,
|
markerRadius * 2.0f, markerRadius * 2.0f);
|
||||||
outerRadius * 2.0f);
|
gd::Pen markerPen(ToGdiColor(0xFFFFFFFFu), 2.0f * m_scale);
|
||||||
const float innerRadius = 3.4f * m_scale;
|
g.DrawEllipse(&markerPen, markerX - markerRadius, markerY - markerRadius,
|
||||||
g.DrawEllipse(&innerPen, markerX - innerRadius, markerY - innerRadius, innerRadius * 2.0f,
|
markerRadius * 2.0f, markerRadius * 2.0f);
|
||||||
innerRadius * 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,
|
const double hue = CurrentHue();
|
||||||
m_hueRect.Width + 4.0f * m_scale, 3.0f * m_scale),
|
const std::array<double, 3> hueRgb = FromHsv(hue, 1.0, 1.0);
|
||||||
0xFF1B1B1Bu);
|
const std::uint32_t hueColor =
|
||||||
UiDrawRoundRect(g, m_hueRect, 2.0f * m_scale, m_theme->borderStrong, kGamutBorder);
|
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)
|
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 std::size_t slot = static_cast<std::size_t>(index);
|
||||||
const gd::RectF& track = m_tracks[slot];
|
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);
|
kLabelWidth * m_scale, kRowHeight * m_scale);
|
||||||
UiText(g, kChannelNames[index], m_fonts->uiSmall, labelRect, m_theme->textMuted, TextAlign::LEFT);
|
UiText(g, kChannelNames[index], m_fonts->uiSmall, labelRect, m_theme->textMuted, TextAlign::LEFT);
|
||||||
|
|
||||||
|
|||||||
@@ -1252,12 +1252,35 @@ namespace jdi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::vector<bool>& fileVisible = m_visible[static_cast<std::size_t>(fileIndex)];
|
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);
|
std::fill(fileVisible.begin(), fileVisible.end(), visible);
|
||||||
SyncLanes();
|
SyncLanes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabConfig::SetAllVisible(bool visible)
|
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)
|
for (std::vector<bool>& fileVisible : m_visible)
|
||||||
{
|
{
|
||||||
std::fill(fileVisible.begin(), fileVisible.end(), visible);
|
std::fill(fileVisible.begin(), fileVisible.end(), visible);
|
||||||
|
|||||||
Reference in new issue
Block a user