fix: 重构样式区交互与布局,修复拾色浮窗穿透与自定义颜色记忆

样式编辑区统一为固定四行网格(颜色 / 样式 / 透明度 / 线宽):
- 四行共用同一内容列宽度,宽度带上下限(面板拉宽时不再无限延伸),
  标签列与内容列作为整体在面板内居中,行标签统一按整行垂直居中,
  修复“颜色”字样与色块存在高低差的问题。
- 颜色行的色块间距按剩余空间自适应,使该行与其它三行等宽对齐。
- 线宽改为与透明度同款的滑块(0.5–10 px),从原先的“预设 + 数值旋钮”
  调整为置于样式区最下方的滑块,保留双击数值直接键入的能力。

自定义颜色浮窗(ColorPickerPopup):
- 拖动滑块时不再与下方图层(尤其是绘图区平移/框选)产生交互:
  拾色浮窗内的按下会被完整消费并抓取鼠标,不再下传。
- 打开浮窗时光标正确显示为横向调整(IDC_SIZEWE),其余区域为箭头。
- 新增自定义颜色记忆:SignalStyle 记录 customColor,从自定义色切换到
  预设色后再次点击加号方格即可直接回到上次的自定义颜色;
  加号方格始终以该颜色填充(与预设色块同尺寸、同外框)。
- 打开浮窗时以记忆色为初始取值,不再重新从预设色起步。

透明度:
- 预设色块与自定义颜色方格同样叠加不透明度显示,
  修复此前透明度只影响自定义颜色预览的问题。

其他:
- 左侧面板宽度上限由 560 收紧至 400,避免样式控件被拉得过长。
- 移除只写不读的色块尺寸缓存与已废弃的线宽步进接口。

验证:真实鼠标输入逐项核对(拾色浮窗拖拽后绘图区视图范围与时间轴完全
不变、滑块光标 SIZEWE、自定义色 #0071C4 在切换预设后仍可一键恢复、
透明度 21% 时全部预设色块同步变淡、线宽滑块 8.1 px / 双击键入 5 设为
5.0 px、面板最窄与最宽两种极限下四行均不溢出且等宽)。
make clean && make all 零错误零警告。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-09-29 18:06:34 +08:00
1 parent 2d09935063
commit dbe9b904ab
6 files changed
+271 -284

No files matched your search

+17 -20
View File
@@ -246,12 +246,10 @@ namespace jdi
NONE = 0, /**< 未命中。 */
COLOR, /**< 预设颜色色块。 */
CUSTOM_CHIP, /**< 自定义颜色入口。 */
WIDTH_PRESET, /**< 线宽预设按钮(细/粗)。 */
WIDTH_MINUS, /**< 线宽减号。 */
WIDTH_PLUS, /**< 线宽加号。 */
WIDTH_VALUE, /**< 线宽数值框。 */
MODE, /**< 绘制样式按钮。 */
OPACITY, /**< 不透明度滑块。 */
OPACITY_SLIDER, /**< 不透明度滑块。 */
WIDTH_SLIDER, /**< 线宽滑块。 */
WIDTH_VALUE, /**< 线宽数值框(可双击输入)。 */
TOGGLE_ALL /**< 全部显示/隐藏按钮。 */
};
@@ -408,13 +406,6 @@ namespace jdi
*/
int TotalSignalCount() const;
/**
* @brief 调整当前信号线宽。
* @param [IN] delta 增量,正值加粗。
* @return 取值发生变化返回 true。
*/
bool StepLineWidth(float delta);
/**
* @brief 进入线宽数值输入状态。
* @return 状态发生变化返回 true。
@@ -435,6 +426,13 @@ namespace jdi
*/
bool ApplyOpacityFromX(float x);
/**
* @brief 依据线宽滑块横坐标调整当前信号线宽。
* @param [IN] x 客户区横坐标。
* @return 取值发生变化返回 true。
*/
bool ApplyWidthFromX(float x);
HWND m_host = nullptr; /**< 宿主窗口句柄。 */
const UiFonts* m_fonts = nullptr; /**< 字体资源。 */
const Theme* m_theme = nullptr; /**< 主题资源。 */
@@ -455,19 +453,17 @@ namespace jdi
std::array<gd::RectF, 12> m_colorRects; /**< 预设颜色色块矩形。 */
int m_colorCount = 0; /**< 实际可用的预设颜色数量。 */
gd::RectF m_customChipRect; /**< 自定义颜色入口矩形(与预设色块同尺寸)。 */
float m_swatchSize = 18.0f; /**< 颜色色块边长(逻辑像素)。 */
gd::RectF m_widthThinRect; /**< 最细线宽预设矩形。 */
gd::RectF m_widthThickRect; /**< 最粗线宽预设矩形。 */
gd::RectF m_widthMinusRect; /**< 线宽减号矩形。 */
gd::RectF m_widthPlusRect; /**< 线宽加号矩形。 */
gd::RectF m_widthValueRect; /**< 线宽数值显示矩形。 */
gd::RectF m_widthSpinnerRect; /**< 线宽数值输入框整体矩形。 */
std::array<gd::RectF, 3> m_modeRects; /**< 绘制样式按钮矩形。 */
// 样式区固定四行:颜色 / 样式 / 透明度 / 线宽。各行控件共用同一内容列宽度。
std::array<gd::RectF, 4> m_styleRowRects; /**< 四行整体矩形(含标签列)。 */
gd::RectF m_styleContentRect; /**< 四行共用的内容列矩形。 */
gd::RectF m_opacityTrackRect; /**< 不透明度滑块轨道矩形。 */
gd::RectF m_opacityValueRect; /**< 不透明度数值文本矩形。 */
gd::RectF m_widthTrackRect; /**< 线宽滑块轨道矩形。 */
gd::RectF m_widthValueRect; /**< 线宽数值文本矩形(双击可输入)。 */
std::vector<ListRow> m_rows; /**< 列表行集合(惰性缓存)。 */
bool m_rowsDirty = true; /**< 列表行集合是否需要重建。 */
@@ -479,6 +475,7 @@ namespace jdi
bool m_draggingScrollBar = false; /**< 是否正在拖拽滚动条。 */
int m_scrollGrabOffset = 0; /**< 拖拽时鼠标相对滑块顶部的偏移。 */
bool m_draggingOpacity = false; /**< 是否正在拖拽不透明度滑块。 */
bool m_draggingWidth = false; /**< 是否正在拖拽线宽滑块。 */
int m_hoverRow = -1; /**< 当前悬停行。 */
RowPart m_hoverPart = RowPart::NONE; /**< 当前悬停的行内区域。 */
StyleHit m_hoverStyle = StyleHit::NONE; /**< 当前悬停的样式条目类型。 */
+202 -256
View File
@@ -25,26 +25,26 @@ namespace jdi
const float kScrollBarThickness = 8.0f; /**< 滚动条宽度。 */
const float kCloseButtonSize = 18.0f; /**< 文件行关闭按钮尺寸。 */
// 样式编辑区:固定四行网格,行高与间距恒定,保证面板宽度变化时控件不散架。
// 样式编辑区:固定四行网格(颜色 / 样式 / 透明度 / 线宽),行高、间距与内容列宽度统一,
// 保证面板宽度变化时四行控件始终对齐,且不会被无限制拉伸。
const float kStylePadH = 12.0f; /**< 样式区左右内边距。 */
const float kStylePadV = 12.0f; /**< 样式区上下内边距。 */
const float kStyleRowHeight = 26.0f; /**< 样式区单行高度。 */
const float kStyleRowGap = 10.0f; /**< 样式区行间距。 */
const float kStyleLabelWidth = 44.0f; /**< 样式行标签列宽度。 */
const float kStyleLabelGap = 8.0f; /**< 标签列与内容列间距。 */
const float kStyleRowCount = 4.0f; /**< 样式区固定行数。 */
const float kSwatchEdge = 18.0f; /**< 颜色色块边长(含自定义颜色入口)。 */
const float kSwatchGap = 5.0f; /**< 颜色色块间距。 */
const float kWidthPresetWidth = 46.0f; /**< 线宽预设按钮宽度。 */
const float kWidthSpinButton = 22.0f; /**< 线宽旋钮按钮宽度。 */
const float kWidthValueWidth = 62.0f; /**< 线宽数值框宽度。 */
const float kOpacityValueWidth = 52.0f; /**< 不透明度数值宽度。 */
const float kStyleContentMax = 260.0f; /**< 内容列最大宽度(再拉宽面板也不再延伸)。 */
const float kSwatchEdge = 18.0f; /**< 颜色色块边长上限(含自定义颜色入口)。 */
const float kSwatchMin = 11.0f; /**< 颜色色块边长下限。 */
const float kSwatchGap = 4.0f; /**< 颜色色块最小间距。 */
const float kSwatchMaxGap = 10.0f; /**< 颜色色块最大间距(用于铺满内容列)。 */
const float kSliderValueWidth = 52.0f; /**< 滑块行数值文本宽度(透明度与线宽一致)。 */
const float kSliderValueGap = 8.0f; /**< 滑块轨道与数值文本间距。 */
const float kTrackHeight = 6.0f; /**< 滑块轨道高度。 */
const float kWidthMin = 0.5f; /**< 允许的最小线宽。 */
const float kWidthMax = 6.0f; /**< 允许的最大线宽。 */
const float kWidthStep = 0.2f; /**< 线宽增减步进。 */
const float kWidthThinPreset = 1.0f; /**< 最细线宽预设值。 */
const float kWidthThickPreset = 3.0f; /**< 最粗线宽预设值。 */
const float kWidthMin = 0.5f; /**< 允许的最小线宽(px)。 */
const float kWidthMax = 10.0f; /**< 允许的最大线宽(px)。 */
/** @brief 可选绘制样式顺序(不含阶梯方式)。 */
const DrawMode kModeOptions[] = {DrawMode::LINE, DrawMode::SCATTER, DrawMode::LINE_SCATTER};
@@ -154,6 +154,7 @@ namespace jdi
m_rowsDirty = true;
m_scrollOffset = 0.0f;
m_draggingOpacity = false;
m_draggingWidth = false;
m_widthEditing = false;
m_widthEditText.clear();
m_pendingLaneMenu = false;
@@ -435,102 +436,104 @@ namespace jdi
void SignalPanel::LayoutStyleControls()
{
const float padH = kStylePadH * m_scale;
const float labelWidth = kStyleLabelWidth * m_scale;
const float contentLeft = m_styleRect.X + kStylePadH * m_scale + labelWidth;
const float contentWidth = std::max(60.0f, m_styleRect.Width - kStylePadH * 2.0f * m_scale - labelWidth);
const float labelGap = kStyleLabelGap * m_scale;
const float rowHeight = kStyleRowHeight * m_scale;
const float row0Top = m_styleRect.Y + kStylePadV * m_scale;
const float rowStep = (kStyleRowHeight + kStyleRowGap) * m_scale;
const float firstTop = m_styleRect.Y + kStylePadV * m_scale;
// ---- 第一行:颜色(预设色块 + 同尺寸自定义颜色入口)----
// 内容列宽度带上下限:面板变窄时收缩,面板变宽时不再延伸,四行始终共用同一宽度。
const float available = std::max(40.0f, m_styleRect.Width - padH * 2.0f - labelWidth - labelGap);
const float contentWidth = std::min(available, kStyleContentMax * m_scale);
// 标签列 + 内容列作为整体在面板内居中,避免拉宽后控件偏向一侧。
const float groupWidth = labelWidth + labelGap + contentWidth;
const float groupLeft = m_styleRect.X + std::max(padH, (m_styleRect.Width - groupWidth) * 0.5f);
const float contentLeft = groupLeft + labelWidth + labelGap;
m_styleContentRect = gd::RectF(contentLeft, firstTop, contentWidth,
kStyleRowCount * rowHeight + (kStyleRowCount - 1.0f) *
(kStyleRowGap * m_scale));
for (int index = 0; index < static_cast<int>(kStyleRowCount); ++index)
{
m_styleRowRects[static_cast<std::size_t>(index)] =
gd::RectF(groupLeft, firstTop + static_cast<float>(index) * rowStep, groupWidth, rowHeight);
}
const gd::RectF& colorRow = m_styleRowRects[0];
const gd::RectF& modeRow = m_styleRowRects[1];
const gd::RectF& opacityRow = m_styleRowRects[2];
const gd::RectF& widthRow = m_styleRowRects[3];
// ---- 颜色行:预设色块 + 同尺寸自定义颜色入口(合计恰好铺满内容列宽度)----
int paletteCount = 0;
SignalPalette(paletteCount);
float swatchSize = kSwatchEdge * m_scale;
const float swatchGap = kSwatchGap * m_scale;
const float swatchMaxGap = kSwatchMaxGap * m_scale;
const float swatchMin = kSwatchMin * m_scale;
int count = std::min(paletteCount, static_cast<int>(m_colorRects.size()));
// 为自定义颜色入口预留一个同尺寸格位,优先整体缩小色块以保留全部预设颜色。
if (count > 0)
float swatchSize = kSwatchEdge * m_scale;
float gap = swatchGap;
while (count >= 1)
{
const float fit = (contentWidth - swatchGap * static_cast<float>(count)) /
static_cast<float>(count + 1);
if (fit < swatchSize)
const float slots = static_cast<float>(count + 1);
const float fit = (contentWidth - swatchGap * (slots - 1.0f)) / slots;
if (count == 1 || fit >= swatchMin)
{
swatchSize = fit;
}
// 色块小于可用下限时再从末尾裁剪预设,避免尺寸不可用。
const float minSwatch = 14.0f * m_scale;
while (count > 1 && swatchSize < minSwatch)
{
--count;
swatchSize = (contentWidth - swatchGap * static_cast<float>(count)) /
static_cast<float>(count + 1);
swatchSize = std::max(swatchMin, std::min(kSwatchEdge * m_scale, fit));
// 用实际色块尺寸反算间距,让颜色行与透明度、线宽、样式各行同宽。
gap = (contentWidth - swatchSize * slots) / static_cast<float>(count);
gap = std::max(swatchGap, std::min(gap, swatchMaxGap));
break;
}
--count;
}
m_colorCount = std::max(0, count);
m_swatchSize = swatchSize / m_scale;
m_colorCount = std::max(1, count);
const float swatchTop = row0Top + (rowHeight - swatchSize) * 0.5f;
// 若受色块尺寸上限约束仍有剩余空间,则整行在内容列内居中。
const float colorRowWidth = swatchSize * static_cast<float>(m_colorCount + 1) +
gap * static_cast<float>(m_colorCount);
const float colorRowLeft = contentLeft + std::max(0.0f, (contentWidth - colorRowWidth) * 0.5f);
const float swatchTop = colorRow.Y + (rowHeight - swatchSize) * 0.5f;
for (int index = 0; index < m_colorCount; ++index)
{
const float x = contentLeft + static_cast<float>(index) * (swatchSize + swatchGap);
m_colorRects[static_cast<std::size_t>(index)] = gd::RectF(x, swatchTop, swatchSize, swatchSize);
m_colorRects[static_cast<std::size_t>(index)] =
gd::RectF(colorRowLeft + static_cast<float>(index) * (swatchSize + gap), swatchTop,
swatchSize, swatchSize);
}
m_customChipRect =
gd::RectF(contentLeft + static_cast<float>(m_colorCount) * (swatchSize + swatchGap), swatchTop,
gd::RectF(colorRowLeft + static_cast<float>(m_colorCount) * (swatchSize + gap), swatchTop,
swatchSize, swatchSize);
// ---- 第二行:线宽(最细 / 数值旋钮 / 最粗)----
// 先按设计尺寸排布,仅在可用宽度不足时整体等比缩小,保证不溢出也不被拉伸变形。
const float row1Top = row0Top + (kStyleRowHeight + kStyleRowGap) * m_scale;
const float naturalGap = 8.0f * m_scale;
const float naturalPreset = kWidthPresetWidth * m_scale;
const float naturalSpinner = (kWidthSpinButton * 2.0f + kWidthValueWidth) * m_scale;
const float naturalTotal = naturalPreset * 2.0f + naturalSpinner + naturalGap * 2.0f;
const float widthFactor =
(naturalTotal > contentWidth && naturalTotal > 0.0f) ? (contentWidth / naturalTotal) : 1.0f;
const float presetWidth = naturalPreset * widthFactor;
const float spinnerWidth = naturalSpinner * widthFactor;
const float rowGap = std::max(2.0f * m_scale, naturalGap * widthFactor);
const float spinButtonWidth = std::min(kWidthSpinButton * m_scale, spinnerWidth * 0.26f);
const float innerValueWidth = std::max(12.0f * m_scale, spinnerWidth - spinButtonWidth * 2.0f);
const float groupWidth = presetWidth * 2.0f + spinnerWidth + rowGap * 2.0f;
const float groupLeft = contentLeft + std::max(0.0f, (contentWidth - groupWidth) * 0.5f);
m_widthThinRect = gd::RectF(groupLeft, row1Top, presetWidth, rowHeight);
m_widthSpinnerRect = gd::RectF(groupLeft + presetWidth + rowGap, row1Top, spinnerWidth, rowHeight);
m_widthMinusRect = gd::RectF(m_widthSpinnerRect.X, row1Top, spinButtonWidth, rowHeight);
m_widthValueRect =
gd::RectF(m_widthMinusRect.X + spinButtonWidth, row1Top, innerValueWidth, rowHeight);
m_widthPlusRect =
gd::RectF(m_widthValueRect.X + innerValueWidth, row1Top, spinButtonWidth, rowHeight);
m_widthThickRect =
gd::RectF(m_widthSpinnerRect.X + spinnerWidth + rowGap, row1Top, presetWidth, rowHeight);
// ---- 第三行:样式(线段 / 散点 / 点线)----
const float row2Top = row1Top + (kStyleRowHeight + kStyleRowGap) * m_scale;
// ---- 样式行:三个等宽按钮铺满内容列 ----
const float modeGap = 6.0f * m_scale;
const float modeWidth = (contentWidth - modeGap * static_cast<float>(kModeOptionCount - 1)) /
static_cast<float>(kModeOptionCount);
const float modeWidth =
std::max(20.0f, (contentWidth - modeGap * static_cast<float>(kModeOptionCount - 1)) /
static_cast<float>(kModeOptionCount));
for (int index = 0; index < kModeOptionCount; ++index)
{
const float x = contentLeft + static_cast<float>(index) * (modeWidth + modeGap);
m_modeRects[static_cast<std::size_t>(index)] = gd::RectF(x, row2Top, modeWidth, rowHeight);
m_modeRects[static_cast<std::size_t>(index)] =
gd::RectF(contentLeft + static_cast<float>(index) * (modeWidth + modeGap), modeRow.Y,
modeWidth, rowHeight);
}
// ---- 第四行:不透明度 ----
const float row3Top = row2Top + (kStyleRowHeight + kStyleRowGap) * m_scale;
const float opacityValueWidth = kOpacityValueWidth * m_scale;
const float trackWidth = std::max(40.0f, contentWidth - opacityValueWidth - 8.0f * m_scale);
m_opacityTrackRect = gd::RectF(contentLeft, row3Top + (rowHeight - kTrackHeight * m_scale) * 0.5f,
trackWidth, kTrackHeight * m_scale);
m_opacityValueRect = gd::RectF(contentLeft + trackWidth + 8.0f * m_scale, row3Top,
opacityValueWidth - 8.0f * m_scale, rowHeight);
// ---- 透明度行与线宽行:几何完全一致,保证纵向宽度对齐 ----
const float valueWidth = std::min(kSliderValueWidth * m_scale, contentWidth * 0.4f);
const float trackWidth = std::max(24.0f, contentWidth - valueWidth - kSliderValueGap * m_scale);
const float trackHeight = kTrackHeight * m_scale;
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_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);
}
void SignalPanel::Layout(const gd::RectF& area)
@@ -835,27 +838,35 @@ namespace jdi
return;
}
const float labelX = m_styleRect.X + kStylePadH * m_scale;
const float labelWidth = kStyleLabelWidth * m_scale;
const float rowHeight = kStyleRowHeight * m_scale;
// ---- 第一行:颜色 ----
// 行标签统一以整行为基准垂直居中,确保与同行控件纵向对齐。
auto paintRowLabel = [&](int rowIndex, const wchar_t* text)
{
UiText(g, L"颜色", m_fonts->uiSmall, gd::RectF(labelX, m_customChipRect.Y, labelWidth, rowHeight),
const gd::RectF& row = m_styleRowRects[static_cast<std::size_t>(rowIndex)];
UiText(g, text, m_fonts->uiSmall, gd::RectF(row.X, row.Y, labelWidth, row.Height),
m_theme->textMuted, TextAlign::LEFT);
};
// ---- 第一行:颜色(预设色块 + 自定义颜色入口)----
{
paintRowLabel(0, L"颜色");
int paletteCount = 0;
const std::uint32_t* palette = SignalPalette(paletteCount);
bool presetActive = false;
// 当自定义颜色与某个预设色恰好相同时,优先视为自定义颜色,避免两处同时高亮。
const bool customActive = (style->color == style->customColor);
for (int index = 0; index < m_colorCount; ++index)
{
const gd::RectF& rect = m_colorRects[static_cast<std::size_t>(index)];
const std::uint32_t color = palette[index % paletteCount];
const bool hovered = (m_hoverStyle == StyleHit::COLOR && m_hoverStyleIndex == index);
const bool active = (style->color == color);
presetActive = presetActive || active;
const bool active = !customActive && (style->color == color);
UiFillRoundRect(g, rect, kCorner * m_scale, color);
// 预设色块同样叠加不透明度,直观反映实际显示效果。
UiFillRoundRect(g, rect, kCorner * m_scale, ApplyOpacity(color, style->opacity));
if (active || hovered)
{
const gd::RectF ring(rect.X - 2.0f * m_scale, rect.Y - 2.0f * m_scale,
@@ -867,125 +878,32 @@ namespace jdi
}
// 自定义颜色入口:与预设色块完全同尺寸、同外框样式的小方格,仅多一个加号图标。
// 当前颜色不在预设中时,方格直接填充该颜色,与预设色块的表现保持一致。
// 方格始终显示记忆的自定义颜色,因此从任意预设色再次点击它都能立即回到上次的自定义色。
const bool chipHot = (m_hoverStyle == StyleHit::CUSTOM_CHIP);
std::uint32_t chipColor = m_theme->cardBg;
std::uint32_t iconColor = chipHot ? m_theme->accent : m_theme->text;
if (!presetActive)
{
chipColor = ApplyOpacity(style->color, style->opacity);
iconColor = IsDarkColor(chipColor) ? 0xFFFDF6E3u : 0xFF073642u;
}
const std::uint32_t chipColor = ApplyOpacity(style->customColor, style->opacity);
const std::uint32_t iconColor = IsDarkColor(chipColor) ? 0xFFFDF6E3u : 0xFF073642u;
UiFillRoundRect(g, m_customChipRect, kCorner * m_scale, chipColor);
UiDrawRoundRect(g, m_customChipRect, kCorner * m_scale,
chipHot ? m_theme->accent : m_theme->borderStrong, 1.0f);
customActive ? m_theme->textStrong : (chipHot ? m_theme->accent : m_theme->borderStrong),
customActive ? 1.6f : 1.0f);
UiDrawPlusIcon(g, m_customChipRect, iconColor, 1.6f * m_scale);
// 选中外框与预设色块完全一致(当前正在使用自定义颜色或被悬停时显示)。
if (!presetActive || chipHot)
// 当前正在使用自定义颜色、或鼠标悬停时,显示与预设色块一致的外框。
if (customActive || chipHot)
{
const gd::RectF& chip = m_customChipRect;
const gd::RectF ring(chip.X - 2.0f * m_scale, chip.Y - 2.0f * m_scale,
chip.Width + 4.0f * m_scale, chip.Height + 4.0f * m_scale);
UiDrawRoundRect(g, ring, 5.0f * m_scale,
!presetActive ? m_theme->textStrong : m_theme->borderStrong,
!presetActive ? 1.6f : 1.0f);
customActive ? m_theme->textStrong : m_theme->borderStrong,
customActive ? 1.6f : 1.0f);
}
}
// ---- 第二行:线宽 ----
// ---- 第二行:样式(线段 / 散点 / 点线)----
{
UiText(g, L"线宽", m_fonts->uiSmall,
gd::RectF(labelX, m_widthThinRect.Y, labelWidth, rowHeight), m_theme->textMuted,
TextAlign::LEFT);
const gd::RectF presetRects[2] = {m_widthThinRect, m_widthThickRect};
const float presetWidths[2] = {kWidthThinPreset, kWidthThickPreset};
for (int index = 0; index < 2; ++index)
{
const gd::RectF& rect = presetRects[index];
const bool active = std::fabs(style->lineWidth - presetWidths[index]) < 0.01f;
const bool hovered =
(m_hoverStyle == StyleHit::WIDTH_PRESET && m_hoverStyleIndex == index);
UiFillRoundRect(g, rect, kCorner * m_scale,
active ? m_theme->accentSoft : (hovered ? m_theme->hover : m_theme->cardBg));
UiDrawRoundRect(g, rect, kCorner * m_scale, active ? m_theme->accent : m_theme->border, 1.0f);
const float centerY = rect.Y + rect.Height * 0.5f;
UiDrawLine(g, rect.X + 9.0f * m_scale, centerY, rect.X + rect.Width - 9.0f * m_scale,
centerY, active ? m_theme->accent : m_theme->text,
presetWidths[index] * m_scale);
}
// 数值旋钮(线宽输入框)。
const bool valueHot = (m_hoverStyle == StyleHit::WIDTH_VALUE);
UiFillRoundRect(g, m_widthSpinnerRect, kCorner * m_scale, m_theme->cardBg);
UiDrawRoundRect(g, m_widthSpinnerRect, kCorner * m_scale,
m_widthEditing ? m_theme->accent
: (valueHot ? m_theme->borderStrong : m_theme->border),
m_widthEditing ? 1.6f : 1.0f);
if (m_widthEditing)
{
const std::wstring editText =
m_widthEditText.empty() ? std::wstring(L" ") : m_widthEditText;
// 面板极窄时省略单位,保证数值本身完整可读。
const std::wstring fullText = editText + L" px";
const std::wstring shown =
(UiTextWidth(g, fullText, m_fonts->monoSmall) > m_widthValueRect.Width - 6.0f * m_scale)
? editText
: fullText;
const float textWidth = UiTextWidth(g, shown, m_fonts->monoSmall);
const float caretX =
m_widthValueRect.X + m_widthValueRect.Width - 6.0f * m_scale - textWidth;
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong,
TextAlign::RIGHT);
if (caretX > m_widthValueRect.X)
{
const float caretY = m_widthValueRect.Y + 5.0f * m_scale;
UiDrawLine(g, caretX, caretY, caretX, m_widthValueRect.Y + rowHeight - 5.0f * m_scale,
m_theme->accent, 1.4f * m_scale);
}
}
else
{
const std::wstring numberText = FormatNumber(style->lineWidth, 1);
const std::wstring fullText = numberText + L" px";
const std::wstring shown =
(UiTextWidth(g, fullText, m_fonts->monoSmall) > m_widthValueRect.Width - 6.0f * m_scale)
? numberText
: fullText;
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong,
TextAlign::CENTER);
}
const bool minusHot = (m_hoverStyle == StyleHit::WIDTH_MINUS);
const bool plusHot = (m_hoverStyle == StyleHit::WIDTH_PLUS);
const bool minusEnabled = style->lineWidth > (kWidthMin + 1.0e-4f);
const bool plusEnabled = style->lineWidth < (kWidthMax - 1.0e-4f);
if (minusHot && minusEnabled)
{
UiFillRoundRect(g, m_widthMinusRect, kCorner * m_scale, m_theme->hover);
}
if (plusHot && plusEnabled)
{
UiFillRoundRect(g, m_widthPlusRect, kCorner * m_scale, m_theme->hover);
}
UiDrawArrowIcon(g, m_widthMinusRect, minusEnabled ? m_theme->text : m_theme->textMuted,
1.6f * m_scale, IconDirection::LEFT);
UiDrawArrowIcon(g, m_widthPlusRect, plusEnabled ? m_theme->text : m_theme->textMuted,
1.6f * m_scale, IconDirection::RIGHT);
}
// ---- 第三行:样式 ----
{
UiText(g, L"样式", m_fonts->uiSmall, gd::RectF(labelX, m_modeRects[0].Y, labelWidth, rowHeight),
m_theme->textMuted, TextAlign::LEFT);
paintRowLabel(1, L"样式");
for (int index = 0; index < kModeOptionCount; ++index)
{
@@ -1001,13 +919,11 @@ namespace jdi
}
}
// ---- 第四行:不透明度 ----
// ---- 第三行:透明度 ----
{
UiText(g, L"透明度", m_fonts->uiSmall,
gd::RectF(labelX, m_opacityValueRect.Y, labelWidth, rowHeight), m_theme->textMuted,
TextAlign::LEFT);
paintRowLabel(2, L"透明度");
const bool active = m_draggingOpacity || (m_hoverStyle == StyleHit::OPACITY);
const bool active = m_draggingOpacity || (m_hoverStyle == StyleHit::OPACITY_SLIDER);
DrawSlider(g, m_opacityTrackRect, static_cast<double>(style->opacity), m_theme->accent, active,
m_scale);
@@ -1015,6 +931,47 @@ namespace jdi
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
m_theme->textStrong, TextAlign::RIGHT);
}
// ---- 第四行:线宽(与透明度行几何一致)----
{
paintRowLabel(3, L"线宽");
const double ratio =
(static_cast<double>(style->lineWidth) - kWidthMin) / (kWidthMax - kWidthMin);
const bool active = m_draggingWidth || (m_hoverStyle == StyleHit::WIDTH_SLIDER);
DrawSlider(g, m_widthTrackRect, ratio, m_theme->accent, active, m_scale);
if (m_widthEditing)
{
// 输入状态:右对齐并显示插入符,便于用户看到键入内容。
const std::wstring editText = m_widthEditText.empty() ? std::wstring(L" ") : m_widthEditText;
UiFillRoundRect(g, m_widthValueRect, kCorner * m_scale, m_theme->cardBg);
UiDrawRoundRect(g, m_widthValueRect, kCorner * m_scale, m_theme->accent, 1.6f);
const float textWidth = UiTextWidth(g, editText, m_fonts->monoSmall);
UiText(g, editText, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong,
TextAlign::RIGHT);
const float caretX = m_widthValueRect.X + m_widthValueRect.Width - 6.0f * m_scale - textWidth;
if (caretX > m_widthValueRect.X)
{
UiDrawLine(g, caretX, m_widthValueRect.Y + 5.0f * m_scale, caretX,
m_widthValueRect.Y + rowHeight - 5.0f * m_scale, m_theme->accent,
1.4f * m_scale);
}
}
else
{
const std::wstring numberText = FormatNumber(style->lineWidth, 1);
const std::wstring fullText = numberText + L" px";
// 空间不足时省略单位,保证数值本身完整可读。
const std::wstring shown =
(UiTextWidth(g, fullText, m_fonts->monoSmall) > m_widthValueRect.Width - 6.0f * m_scale)
? numberText
: fullText;
UiText(g, shown, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong, TextAlign::RIGHT);
}
}
}
bool SignalPanel::AreAllSignalsVisible() const
@@ -1026,7 +983,7 @@ namespace jdi
return m_tab->VisibleCount() == TotalSignalCount();
}
bool SignalPanel::StepLineWidth(float delta)
bool SignalPanel::ApplyWidthFromX(float x)
{
if (m_tab == nullptr)
{
@@ -1038,8 +995,10 @@ namespace jdi
return false;
}
float value = ClampFloat(style->lineWidth + delta, kWidthMin, kWidthMax);
const double ratio = UiSliderRatioFromX(m_widthTrackRect, x);
float value = static_cast<float>(kWidthMin + ratio * (kWidthMax - kWidthMin));
value = std::round(value * 10.0f) / 10.0f;
value = ClampFloat(value, kWidthMin, kWidthMax);
if (std::fabs(value - style->lineWidth) < 0.001f)
{
return false;
@@ -1191,12 +1150,12 @@ namespace jdi
{
return false;
}
if (style->color == color)
{
return false;
}
// 同时记住最后一次自定义取值,便于切换到预设色后一键回到该颜色。
const bool changed = (style->color != color) || (style->customColor != color);
style->color = color;
return true;
style->customColor = color;
return changed;
}
bool SignalPanel::ApplyOpacityFromX(float x)
@@ -1228,7 +1187,8 @@ namespace jdi
}
const gd::RectF bounds = (m_clientBounds.Width > 1.0f) ? m_clientBounds : m_area;
m_colorPicker->Open(bounds, m_customChipRect, style.color);
// 以记忆的自定义颜色作为初始取值,实现“再次打开即恢复上次取色”。
m_colorPicker->Open(bounds, m_customChipRect, style.customColor);
return true;
}
@@ -1254,6 +1214,10 @@ namespace jdi
{
return ApplyOpacityFromX(static_cast<float>(x));
}
if (m_draggingWidth)
{
return ApplyWidthFromX(static_cast<float>(x));
}
if (m_draggingScrollBar)
{
const gd::RectF thumb = ScrollThumbRect();
@@ -1296,28 +1260,6 @@ namespace jdi
{
hoverStyle = StyleHit::CUSTOM_CHIP;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthThinRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_PRESET;
hoverStyleIndex = 0;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthThickRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_PRESET;
hoverStyleIndex = 1;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthMinusRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_MINUS;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthPlusRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_PLUS;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthValueRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_VALUE;
}
if (hoverStyle == StyleHit::NONE)
{
for (int index = 0; index < kModeOptionCount; ++index)
@@ -1333,7 +1275,16 @@ namespace jdi
if (hoverStyle == StyleHit::NONE &&
UiPointInRect(SliderHitRect(m_opacityTrackRect, m_scale), fx, fy))
{
hoverStyle = StyleHit::OPACITY;
hoverStyle = StyleHit::OPACITY_SLIDER;
}
if (hoverStyle == StyleHit::NONE &&
UiPointInRect(SliderHitRect(m_widthTrackRect, m_scale), fx, fy))
{
hoverStyle = StyleHit::WIDTH_SLIDER;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthValueRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_VALUE;
}
}
@@ -1416,33 +1367,11 @@ namespace jdi
}
if (UiPointInRect(m_customChipRect, fx, fy))
{
// 在左键抬起时展开,避免按下即被后续消息立即收起。
// 先恢复到记忆的自定义颜色(即便随后收起浮窗也会生效),再在抬起时展开浮窗。
style->color = style->customColor;
m_pendingColorPicker = true;
return true;
}
if (UiPointInRect(m_widthThinRect, fx, fy))
{
style->lineWidth = kWidthThinPreset;
return true;
}
if (UiPointInRect(m_widthThickRect, fx, fy))
{
style->lineWidth = kWidthThickPreset;
return true;
}
if (UiPointInRect(m_widthMinusRect, fx, fy))
{
return StepLineWidth(-kWidthStep);
}
if (UiPointInRect(m_widthPlusRect, fx, fy))
{
return StepLineWidth(kWidthStep);
}
if (UiPointInRect(m_widthValueRect, fx, fy))
{
// 单击即进入输入状态,双击亦等效。
return BeginWidthEdit();
}
for (int index = 0; index < kModeOptionCount; ++index)
{
if (UiPointInRect(m_modeRects[static_cast<std::size_t>(index)], fx, fy))
@@ -1454,7 +1383,19 @@ namespace jdi
if (UiPointInRect(SliderHitRect(m_opacityTrackRect, m_scale), fx, fy))
{
m_draggingOpacity = true;
return ApplyOpacityFromX(fx) || true;
ApplyOpacityFromX(fx);
return true;
}
if (UiPointInRect(SliderHitRect(m_widthTrackRect, m_scale), fx, fy))
{
m_draggingWidth = true;
ApplyWidthFromX(fx);
return true;
}
if (UiPointInRect(m_widthValueRect, fx, fy))
{
// 单击即进入输入状态,双击亦等效。
return BeginWidthEdit();
}
}
@@ -1518,6 +1459,11 @@ namespace jdi
m_draggingOpacity = false;
changed = true;
}
if (m_draggingWidth)
{
m_draggingWidth = false;
changed = true;
}
if (m_draggingScrollBar)
{
m_draggingScrollBar = false;