fix: 修正滑块填充残留、数值内联输入与信号/分组重命名,拖拽虚影跟随鼠标

- 滑块最小值时不再有超出圆钮的填充残留,三处滑块统一为共享实现
- 透明度/RGB/色号支持点击或双击直接输入,整段高亮、首次键入替换、回车提交、Ctrl+C 复制
- 双击信号行或分组名进入重命名编辑;分组竖线改为绘制而非字符
- 拖拽信号时曲线虚影跟随鼠标纵向位移
- 数据层新增展示名覆盖,面板改为持有可写数据集合
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-09-30 09:38:22 +08:00
1 parent 542aa51e40
commit 261b8ac07d
13 files changed
+1074 -274

No files matched your search

+359 -175
View File
@@ -24,6 +24,8 @@ namespace jdi
const float kIndent = 14.0f; /**< 信号行相对文件行的缩进。 */
const float kScrollBarThickness = 8.0f; /**< 滚动条宽度。 */
const float kCloseButtonSize = 18.0f; /**< 文件行关闭按钮尺寸。 */
const float kGroupSeparatorGap = 7.0f; /**< 分组标签与分隔线/名称的间距。 */
const float kGroupSeparatorWidth = 1.0f; /**< 分组标签右侧分隔线宽度。 */
// 样式编辑区:固定四行网格(颜色 / 样式 / 透明度 / 线宽),行高、间距与内容列宽度统一,
// 保证面板宽度变化时四行控件始终对齐,且不会被无限制拉伸。
@@ -62,36 +64,6 @@ namespace jdi
track.Height + 18.0f * scale);
}
/**
* @brief 绘制水平滑块(轨道 + 已填充部分 + 圆钮)。
* @param [IN,OUT] g 绘图上下文。
* @param [IN] track 轨道矩形。
* @param [IN] ratio 当前值比例(0-1)。
* @param [IN] fillColor 已填充部分颜色。
* @param [IN] active 是否处于悬停或拖拽状态。
* @param [IN] scale 缩放比例。
*/
void DrawSlider(gd::Graphics& g, const gd::RectF& track, double ratio, std::uint32_t fillColor,
bool active, float scale)
{
const float radius = track.Height * 0.5f;
UiFillRoundRect(g, track, radius, 0x2A657B83u);
ratio = std::max(0.0, std::min(1.0, ratio));
const float fillWidth = std::max(track.Height, static_cast<float>(ratio) * track.Width);
UiFillRoundRect(g, gd::RectF(track.X, track.Y, fillWidth, track.Height), radius, fillColor);
const float knobX = track.X + static_cast<float>(ratio) * track.Width;
const float knobRadius = radius + (active ? 2.5f * scale : 0.8f * scale);
gd::SolidBrush knob(ToGdiColor(0xFFFFFFFFu));
gd::Pen knobPen(ToGdiColor(0xFF586E75u), 1.4f * scale);
g.FillEllipse(&knob, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
knobRadius * 2.0f);
g.DrawEllipse(&knobPen, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
knobRadius * 2.0f);
}
/**
* @brief 判断颜色是否偏暗(用于在色块之上选择可读的图标颜色)。
* @param [IN] color 0xAARRGGBB 颜色值。
@@ -134,16 +106,19 @@ namespace jdi
m_colorPicker = colorPicker;
}
void SignalPanel::SetContext(const DataSet* dataSet, TabConfig* tab)
void SignalPanel::SetContext(DataSet* dataSet, TabConfig* tab)
{
m_dataSet = dataSet;
m_dataSetWritable = dataSet;
m_tab = tab;
m_rowsDirty = true;
m_scrollOffset = 0.0f;
m_draggingOpacity = false;
m_draggingWidth = false;
m_widthEditing = false;
m_widthEditText.clear();
m_editField = StyleEditField::NONE;
m_editRow = -1;
m_editFresh = false;
m_editText.clear();
m_pendingLaneMenu = false;
m_pendingColorPicker = false;
}
@@ -682,22 +657,39 @@ namespace jdi
const gd::RectF closeRect = FileCloseRect(row);
// 分组标题:以“组X | <文件名>”呈现,X 为该组在当前列表中的序号。
const std::wstring groupLabel = L"组" + std::to_wstring(item.fileIndex + 1) + L" | ";
// 分组标题:左侧为“组X”标签,右侧以绘制的竖线分隔分组名(不使用字符),再显示可重命名的名称。
const std::wstring groupLabel = L"组" + std::to_wstring(item.fileIndex + 1);
const std::wstring stats = std::to_wstring(file->signals.size()) + L" 项";
const float groupWidth = UiMeasureTextWidth(m_fonts->uiBold, groupLabel);
const float groupWidth = FileGroupLabelWidth(row);
const float statsWidth = UiMeasureTextWidth(m_fonts->uiSmall, stats);
const float statsRight = closeRect.X - 6.0f * m_scale;
const float nameLeft = checkRect.X + checkRect.Width + 8.0f * m_scale;
const float nameAreaLeft =
nameLeft + groupWidth + kGroupSeparatorGap * 2.0f * m_scale + kGroupSeparatorWidth * m_scale;
const float nameWidth =
std::max(20.0f, statsRight - statsWidth - 6.0f * m_scale - nameLeft - groupWidth);
std::max(20.0f, statsRight - statsWidth - 6.0f * m_scale - nameAreaLeft);
// 序号以强调色突出,便于在多组之间快速定位。
UiText(g, groupLabel, m_fonts->uiBold,
gd::RectF(nameLeft, rect.Y, groupWidth, rect.Height), m_theme->accent, TextAlign::LEFT);
UiText(g, UiEllipsize(g, file->fileName, m_fonts->uiBold, nameWidth), m_fonts->uiBold,
gd::RectF(nameLeft + groupWidth, rect.Y, nameWidth, rect.Height), m_theme->textStrong,
TextAlign::LEFT);
UiText(g, groupLabel, m_fonts->uiBold, gd::RectF(nameLeft, rect.Y, groupWidth, rect.Height),
m_theme->accent, TextAlign::LEFT);
// 竖线分隔符:以绘制方式实现,与文字基线无关,排版更稳。
UiFillRect(g, gd::RectF(nameLeft + groupWidth + kGroupSeparatorGap * m_scale,
rect.Y + 7.0f * m_scale, kGroupSeparatorWidth * m_scale,
rect.Height - 14.0f * m_scale),
WithAlpha(m_theme->borderStrong, 0xB0));
if (m_editField == StyleEditField::FILE_NAME && m_editRow == row)
{
UiDrawEditField(g, RowNameRect(row), m_editText, m_editFresh, m_fonts->uiSmall, m_scale);
}
else
{
UiText(g, UiEllipsize(g, file->Title(), m_fonts->uiBold, nameWidth), m_fonts->uiBold,
gd::RectF(nameAreaLeft, rect.Y, nameWidth, rect.Height), m_theme->textStrong,
TextAlign::LEFT);
}
UiText(g, stats, m_fonts->uiSmall, gd::RectF(statsRight - statsWidth, rect.Y, statsWidth, rect.Height),
m_theme->textMuted, TextAlign::RIGHT);
@@ -766,12 +758,19 @@ namespace jdi
nameRightLimit = badgeRect.X - 6.0f * m_scale;
}
// 名称:完全保留文件内的列名(含 "[n]" 序号与括号描述)。
// 名称:完全保留文件内的列名(含 "[n]" 序号与括号描述),双击可重命名。
const float nameLeft = swatchRect.X + swatchRect.Width + 6.0f * m_scale;
const float nameWidth = std::max(16.0f, nameRightLimit - nameLeft);
const std::uint32_t nameColor = style->visible ? m_theme->text : m_theme->textMuted;
UiText(g, UiEllipsize(g, signal->name, m_fonts->ui, nameWidth), m_fonts->ui,
gd::RectF(nameLeft, rect.Y, nameWidth, rect.Height), nameColor, TextAlign::LEFT);
if (m_editField == StyleEditField::SIGNAL_NAME && m_editRow == row)
{
UiDrawEditField(g, RowNameRect(row), m_editText, m_editFresh, m_fonts->ui, m_scale);
}
else
{
const float nameWidth = std::max(16.0f, nameRightLimit - nameLeft);
const std::uint32_t nameColor = style->visible ? m_theme->text : m_theme->textMuted;
UiText(g, UiEllipsize(g, signal->Title(), m_fonts->ui, nameWidth), m_fonts->ui,
gd::RectF(nameLeft, rect.Y, nameWidth, rect.Height), nameColor, TextAlign::LEFT);
}
if (badgeVisible)
{
@@ -830,7 +829,6 @@ namespace jdi
}
const float labelWidth = kStyleLabelWidth * m_scale;
const float rowHeight = kStyleRowHeight * m_scale;
// 行标签统一以整行为基准垂直居中,确保与同行控件纵向对齐。
auto paintRowLabel = [&](int rowIndex, const wchar_t* text)
@@ -906,65 +904,38 @@ namespace jdi
}
}
// ---- 第三行:透明度 ----
// ---- 第三行:透明度(数值可点击输入)----
{
paintRowLabel(2, L"透明度");
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);
UiDrawSlider(g, m_opacityTrackRect, static_cast<double>(style->opacity), m_theme->accent, active,
m_scale);
const int percent = static_cast<int>(style->opacity * 100.0f + 0.5f);
UiText(g, std::to_wstring(percent) + L"%", m_fonts->monoSmall, m_opacityValueRect,
m_theme->textStrong, TextAlign::RIGHT);
if (m_editField == StyleEditField::OPACITY)
{
UiDrawEditField(g, m_opacityValueRect, m_editText, m_editFresh, m_fonts->monoSmall, m_scale);
}
else
{
const int percent = static_cast<int>(style->opacity * 100.0f + 0.5f);
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);
UiDrawSlider(g, m_widthTrackRect, ratio, m_theme->accent, active, m_scale);
if (m_widthEditing)
if (m_editField == StyleEditField::WIDTH)
{
// 输入状态:数值整体居中;处于“全部选中”状态时以高亮背景表示内容将被覆盖,
// 交互观感与常见登录界面的账号/密码输入框一致。
UiFillRoundRect(g, m_widthValueRect, kCorner * m_scale, m_theme->cardBg);
UiDrawRoundRect(g, m_widthValueRect, kCorner * m_scale, m_theme->accent, 1.6f);
const std::wstring editText = m_widthEditText.empty() ? std::wstring(L" ") : m_widthEditText;
const float textWidth = UiTextWidth(g, editText, m_fonts->monoSmall);
const float textX =
m_widthValueRect.X + std::max(2.0f * m_scale, (m_widthValueRect.Width - textWidth) * 0.5f);
if (m_widthEditFresh)
{
// 高亮选区:覆盖整段数值,提示首次键入即整体替换。
const float padX = 3.0f * m_scale;
UiFillRoundRect(g, gd::RectF(textX - padX, m_widthValueRect.Y + 4.0f * m_scale,
textWidth + padX * 2.0f,
m_widthValueRect.Height - 8.0f * m_scale),
2.0f * m_scale, m_theme->accent);
UiText(g, editText, m_fonts->monoSmall, m_widthValueRect, m_theme->textOnAccent,
TextAlign::CENTER);
}
else
{
UiText(g, editText, m_fonts->monoSmall, m_widthValueRect, m_theme->textStrong,
TextAlign::CENTER);
// 已开始键入:在文本末尾显示插入符。
const float caretX = textX + textWidth + 1.0f * m_scale;
if (caretX < m_widthValueRect.X + m_widthValueRect.Width - 2.0f * m_scale)
{
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);
}
}
UiDrawEditField(g, m_widthValueRect, m_editText, m_editFresh, m_fonts->monoSmall, m_scale);
}
else
{
@@ -1013,115 +984,304 @@ namespace jdi
return true;
}
bool SignalPanel::BeginWidthEdit()
gd::RectF SignalPanel::RowNameRect(int row) const
{
if (m_tab == nullptr)
if (m_dataSet == nullptr || m_tab == nullptr || row < 0 || row >= static_cast<int>(m_rows.size()))
{
return gd::RectF();
}
const gd::RectF rect = RowRect(row);
const bool isFile = (m_rows[static_cast<std::size_t>(row)].kind == RowKind::FILE);
const float baseX = rect.X + kPadding * m_scale + (isFile ? 0.0f : kIndent * m_scale);
const float checkRight = baseX + kDisclosureWidth * m_scale + kCheckSize * m_scale;
float left = 0.0f;
float right = 0.0f;
if (isFile)
{
// 分组名需要跳过左侧的“组X”标签与分隔线。
left = checkRight + 8.0f * m_scale + FileGroupLabelWidth(row) + kGroupSeparatorGap * 2.0f * m_scale +
kGroupSeparatorWidth * m_scale;
right = FileCloseRect(row).X - 6.0f * m_scale;
}
else
{
left = checkRight + 6.0f * m_scale + kSwatchSize * m_scale + 6.0f * m_scale;
right = SignalBadgeRect(row).X - 6.0f * m_scale;
}
if (right <= left)
{
return gd::RectF();
}
return gd::RectF(left, rect.Y + 3.0f * m_scale, right - left, rect.Height - 6.0f * m_scale);
}
float SignalPanel::FileGroupLabelWidth(int row) const
{
if (m_fonts == nullptr || m_fonts->uiBold == nullptr || row < 0 ||
row >= static_cast<int>(m_rows.size()))
{
return 0.0f;
}
const std::wstring label = L"组" + std::to_wstring(m_rows[static_cast<std::size_t>(row)].fileIndex + 1);
return UiMeasureTextWidth(m_fonts->uiBold, label);
}
bool SignalPanel::BeginEdit(StyleEditField field, int row)
{
if (m_tab == nullptr || field == StyleEditField::NONE)
{
return false;
}
const SignalStyle* style = m_tab->Style(m_tab->Selected());
if (style == nullptr)
{
return false;
}
if (m_widthEditing)
if (m_editField == field && m_editRow == row)
{
return false;
}
m_widthEditText = FormatNumber(style->lineWidth, 1);
m_widthEditing = true;
m_widthEditFresh = true;
std::wstring initial;
switch (field)
{
case StyleEditField::WIDTH:
{
const SignalStyle* style = m_tab->Style(m_tab->Selected());
if (style == nullptr)
{
return false;
}
initial = FormatNumber(style->lineWidth, 1);
break;
}
case StyleEditField::OPACITY:
{
const SignalStyle* style = m_tab->Style(m_tab->Selected());
if (style == nullptr)
{
return false;
}
initial = std::to_wstring(static_cast<int>(style->opacity * 100.0f + 0.5f));
break;
}
case StyleEditField::SIGNAL_NAME:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()))
{
return false;
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
const SignalData* signal = m_dataSet->Resolve(SignalRef{item.fileIndex, item.signalIndex});
if (signal == nullptr)
{
return false;
}
initial = signal->Title();
break;
}
case StyleEditField::FILE_NAME:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()))
{
return false;
}
const DataFile* file = m_dataSet->FileAt(m_rows[static_cast<std::size_t>(row)].fileIndex);
if (file == nullptr)
{
return false;
}
initial = file->Title();
break;
}
default:
return false;
}
m_editField = field;
m_editRow = row;
m_editText = std::move(initial);
m_editFresh = true;
return true;
}
bool SignalPanel::EndWidthEdit(bool commit)
bool SignalPanel::EndEdit(bool commit)
{
if (!m_widthEditing)
if (m_editField == StyleEditField::NONE)
{
return false;
}
bool changed = true;
if (commit && m_tab != nullptr)
const StyleEditField field = m_editField;
const int row = m_editRow;
const std::wstring text = m_editText;
m_editField = StyleEditField::NONE;
m_editRow = -1;
m_editFresh = false;
m_editText.clear();
if (!commit || m_tab == nullptr || m_dataSet == nullptr)
{
const std::wstring trimmed = Trim(m_widthEditText);
if (!trimmed.empty())
{
wchar_t* stop = nullptr;
const double parsed = std::wcstod(trimmed.c_str(), &stop);
if (stop != trimmed.c_str() && parsed == parsed)
{
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
{
const float value = ClampFloat(static_cast<float>(parsed), kWidthMin, kWidthMax);
style->lineWidth = std::round(value * 10.0f) / 10.0f;
}
}
}
return true;
}
m_widthEditing = false;
m_widthEditFresh = false;
m_widthEditText.clear();
return changed;
const std::wstring trimmed = Trim(text);
switch (field)
{
case StyleEditField::WIDTH:
{
if (trimmed.empty())
{
break;
}
wchar_t* stop = nullptr;
const double parsed = std::wcstod(trimmed.c_str(), &stop);
if (stop == trimmed.c_str() || parsed != parsed)
{
break;
}
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
{
const float value = ClampFloat(static_cast<float>(parsed), kWidthMin, kWidthMax);
style->lineWidth = std::round(value * 10.0f) / 10.0f;
}
break;
}
case StyleEditField::OPACITY:
{
if (trimmed.empty())
{
break;
}
wchar_t* stop = nullptr;
const double parsed = std::wcstod(trimmed.c_str(), &stop);
if (stop == trimmed.c_str() || parsed != parsed)
{
break;
}
if (SignalStyle* style = m_tab->Style(m_tab->Selected()))
{
style->opacity = ClampFloat(static_cast<float>(parsed) / 100.0f, 0.0f, 1.0f);
}
break;
}
case StyleEditField::SIGNAL_NAME:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()) || m_dataSetWritable == nullptr)
{
break;
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
m_dataSetWritable->SetSignalDisplayName(item.fileIndex, item.signalIndex, trimmed);
break;
}
case StyleEditField::FILE_NAME:
{
if (row < 0 || row >= static_cast<int>(m_rows.size()) || m_dataSetWritable == nullptr)
{
break;
}
m_dataSetWritable->SetFileDisplayName(m_rows[static_cast<std::size_t>(row)].fileIndex, trimmed);
break;
}
default:
break;
}
return true;
}
gd::RectF SignalPanel::EditFieldRect() const
{
switch (m_editField)
{
case StyleEditField::WIDTH:
return m_widthValueRect;
case StyleEditField::OPACITY:
return m_opacityValueRect;
case StyleEditField::SIGNAL_NAME:
case StyleEditField::FILE_NAME:
return RowNameRect(m_editRow);
case StyleEditField::NONE:
default:
return gd::RectF();
}
}
bool SignalPanel::OnEditChar(wchar_t character)
{
if (!m_widthEditing)
if (m_editField == StyleEditField::NONE)
{
return false;
}
// Ctrl+C:把当前文本复制到剪贴板。
if (character == 0x03)
{
CopyTextToClipboard(m_host, m_editText);
return false;
}
if (character == L'\r' || character == L'\n')
{
return EndWidthEdit(true);
return EndEdit(true);
}
if (character == L'\b')
{
m_widthEditFresh = false;
if (!m_widthEditText.empty())
m_editFresh = false;
if (!m_editText.empty())
{
m_widthEditText.pop_back();
m_editText.pop_back();
}
return true;
}
if (character >= L'0' && character <= L'9')
// 名称类字段允许任意可打印字符(含空格、方括号、中文)。
const bool nameField = (m_editField == StyleEditField::SIGNAL_NAME) ||
(m_editField == StyleEditField::FILE_NAME);
if (m_editField == StyleEditField::OPACITY && (character == L'%'))
{
// 首次键入直接替换原有数值,无需先手动删除。
if (m_widthEditFresh)
{
m_widthEditText.clear();
m_widthEditFresh = false;
}
if (m_widthEditText.size() < 5)
{
m_widthEditText.push_back(character);
}
return true;
}
if (character == L'.')
if (character < 0x20 || character == 0x7F)
{
if (m_widthEditFresh)
{
m_widthEditText.clear();
m_widthEditFresh = false;
}
// 只允许一个小数点。
if (m_widthEditText.find(L'.') == std::wstring::npos && m_widthEditText.size() < 4)
{
m_widthEditText.push_back(character);
}
return true;
}
if (nameField && m_editText.size() >= 64)
{
return true;
}
const bool numericField = !nameField;
if (numericField)
{
const bool digit = (character >= L'0' && character <= L'9');
const bool dot = (character == L'.');
if (!digit && !dot)
{
return true;
}
if (dot && m_editText.find(L'.') != std::wstring::npos)
{
return true;
}
if (!dot && m_editText.size() >= 5)
{
return true;
}
}
// 首次键入直接替换原有内容,无需先手动删除。
if (m_editFresh)
{
m_editText.clear();
m_editFresh = false;
}
m_editText.push_back(character);
return true;
}
bool SignalPanel::OnEditKey(WPARAM key)
{
if (!m_widthEditing)
if (m_editField == StyleEditField::NONE)
{
return false;
}
@@ -1129,14 +1289,14 @@ namespace jdi
switch (key)
{
case VK_RETURN:
return EndWidthEdit(true);
return EndEdit(true);
case VK_ESCAPE:
return EndWidthEdit(false);
return EndEdit(false);
case VK_BACK:
m_widthEditFresh = false;
if (!m_widthEditText.empty())
m_editFresh = false;
if (!m_editText.empty())
{
m_widthEditText.pop_back();
m_editText.pop_back();
}
return true;
default:
@@ -1201,9 +1361,9 @@ namespace jdi
bool SignalPanel::CloseTransientUi()
{
bool changed = false;
if (m_widthEditing)
if (m_editField != StyleEditField::NONE)
{
changed = EndWidthEdit(true) || changed;
changed = EndEdit(true) || changed;
}
if (m_colorPicker != nullptr && m_colorPicker->IsOpen())
{
@@ -1288,6 +1448,10 @@ namespace jdi
{
hoverStyle = StyleHit::WIDTH_SLIDER;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_opacityValueRect, fx, fy))
{
hoverStyle = StyleHit::OPACITY_VALUE;
}
if (hoverStyle == StyleHit::NONE && UiPointInRect(m_widthValueRect, fx, fy))
{
hoverStyle = StyleHit::WIDTH_VALUE;
@@ -1344,10 +1508,11 @@ namespace jdi
m_pendingLaneMenu = false;
m_pendingColorPicker = false;
// 点击样式区以外的位置时结束线宽输入。
if (m_widthEditing && !UiPointInRect(m_widthValueRect, fx, fy))
// 点击当前输入框以外的位置时提交输入。
if (m_editField != StyleEditField::NONE &&
!UiPointInRect(EditFieldRect(), fx, fy))
{
EndWidthEdit(true);
EndEdit(true);
}
// 全部显示/隐藏。
@@ -1398,10 +1563,14 @@ namespace jdi
ApplyWidthFromX(fx);
return true;
}
// 数值框的命中优先于滑块:两者几何重叠时要能进入输入状态。
if (UiPointInRect(m_widthValueRect, fx, fy))
{
// 单击即进入输入状态,双击亦等效。
return BeginWidthEdit();
return BeginEdit(StyleEditField::WIDTH);
}
if (UiPointInRect(m_opacityValueRect, fx, fy))
{
return BeginEdit(StyleEditField::OPACITY);
}
}
@@ -1522,10 +1691,14 @@ namespace jdi
const float fx = static_cast<float>(x);
const float fy = static_cast<float>(y);
// 线宽数值框:双击进入(或保持)输入状态。
// 样式区数值框:双击进入(或保持)输入状态。
if (UiPointInRect(m_widthValueRect, fx, fy))
{
return BeginWidthEdit();
return BeginEdit(StyleEditField::WIDTH);
}
if (UiPointInRect(m_opacityValueRect, fx, fy))
{
return BeginEdit(StyleEditField::OPACITY);
}
RowPart part = RowPart::NONE;
@@ -1536,24 +1709,35 @@ namespace jdi
}
const ListRow& item = m_rows[static_cast<std::size_t>(row)];
// 分组行:双击折叠三角用于折叠,双击名称区域进入重命名。
if (item.kind == RowKind::FILE)
{
m_tab->ToggleCollapsed(item.fileIndex);
if (part == RowPart::DISCLOSURE)
{
m_tab->ToggleCollapsed(item.fileIndex);
m_rowsDirty = true;
return true;
}
if (part != RowPart::BODY)
{
return false;
}
// 重命名分组会改变列表行高度以外的展示内容,重建行缓存以保证文字刷新。
m_rowsDirty = true;
return true;
return BeginEdit(StyleEditField::FILE_NAME, row);
}
// 网格徽标单击已可打开菜单,双击不再切换可见性。
if (part == RowPart::LANE_BADGE)
// 复选框与网格徽标各自由单击处理,双击不再切换可见性。
if (part == RowPart::CHECK || part == RowPart::LANE_BADGE)
{
return false;
}
const SignalRef ref{item.fileIndex, item.signalIndex};
m_tab->SetSelected(ref);
m_tab->ToggleVisible(*m_dataSet, ref);
m_rowsDirty = true;
return true;
return BeginEdit(StyleEditField::SIGNAL_NAME, row);
}
bool SignalPanel::OnRButtonDown(int x, int y)
@@ -1630,7 +1814,7 @@ namespace jdi
MenuItem header;
header.kind = MenuItemKind::HEADER;
header.label = signal->name;
header.label = signal->Title();
items.push_back(header);
MenuItem toggle;
@@ -1712,7 +1896,7 @@ namespace jdi
MenuItem header;
header.kind = MenuItemKind::HEADER;
header.label = file->fileName;
header.label = file->Title();
items.push_back(header);
MenuItem showAll;