fix: 修正滑块填充残留、数值内联输入与信号/分组重命名,拖拽虚影跟随鼠标
- 滑块最小值时不再有超出圆钮的填充残留,三处滑块统一为共享实现 - 透明度/RGB/色号支持点击或双击直接输入,整段高亮、首次键入替换、回车提交、Ctrl+C 复制 - 双击信号行或分组名进入重命名编辑;分组竖线改为绘制而非字符 - 拖拽信号时曲线虚影跟随鼠标纵向位移 - 数据层新增展示名覆盖,面板改为持有可写数据集合
This commit is contained in:
1 parent
542aa51e40
commit
261b8ac07d
13 files changed
+1074
-274
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user