#include "SignalPanel.hpp" #include #include #include "Utf8Util.hpp" namespace jdi { namespace { const float kHeaderHeight = 36.0f; /**< 标题区高度。 */ const float kRowHeight = 30.0f; /**< 列表行高。 */ const float kStyleHeight = 182.0f; /**< 样式编辑区高度。 */ const float kPadding = 12.0f; /**< 通用内边距。 */ const float kCheckSize = 15.0f; /**< 复选框尺寸。 */ const float kSwatchSize = 12.0f; /**< 颜色小方块尺寸。 */ const float kBadgeWidth = 30.0f; /**< 网格标签宽度。 */ const float kBadgeHeight = 17.0f; /**< 网格标签高度。 */ const float kCorner = 4.0f; /**< 通用圆角半径。 */ const float kLineLabelWidth = 40.0f; /**< 样式行标签列宽度。 */ /** @brief 上下文菜单命令 ID 基准值。 */ const int kMenuToggleVisible = 1; const int kMenuSolo = 2; const int kMenuShowAll = 3; const int kMenuModeBase = 100; const int kMenuWidthBase = 200; const int kMenuColorBase = 300; const int kMenuLaneBase = 400; const int kMenuLaneNew = 500; /** @brief 可选线宽档位。 */ const float kWidthOptions[] = {1.0f, 1.5f, 2.0f, 3.0f}; /** @brief 可选绘制方式顺序。 */ const DrawMode kModeOptions[] = {DrawMode::LINE, DrawMode::SCATTER, DrawMode::LINE_SCATTER, DrawMode::STEP}; } SignalPanel::SignalPanel() { m_colorRects.fill(gd::RectF()); m_widthRects.fill(gd::RectF()); m_modeRects.fill(gd::RectF()); } void SignalPanel::Setup(HWND host, const UiFonts* fonts, const Theme* theme) { m_host = host; m_fonts = fonts; m_theme = theme; } void SignalPanel::SetDocument(SignalSet* document) { m_document = document; m_scrollOffset = 0.0f; } void SignalPanel::SetScale(float scale) { m_scale = (scale > 0.1f) ? scale : 1.0f; } bool SignalPanel::HitTest(int x, int y) const { return UiPointInRect(m_area, static_cast(x), static_cast(y)); } float SignalPanel::ContentHeight() const { if (m_document == nullptr) { return 0.0f; } return static_cast(m_document->Signals().size()) * kRowHeight * m_scale; } void SignalPanel::ClampScroll() { const float visibleHeight = m_listRect.Height; const float contentHeight = ContentHeight(); const float maxOffset = std::max(0.0f, contentHeight - visibleHeight); m_scrollOffset = std::max(0.0f, std::min(m_scrollOffset, maxOffset)); } int SignalPanel::RowAtY(int y) const { if (m_document == nullptr || m_listRect.Height <= 0.0f) { return -1; } const float fy = static_cast(y); if (fy < m_listRect.Y || fy > m_listRect.Y + m_listRect.Height) { return -1; } const float local = fy - m_listRect.Y + m_scrollOffset; if (local < 0.0f) { return -1; } const int row = static_cast(local / (kRowHeight * m_scale)); if (row < 0 || row >= static_cast(m_document->Signals().size())) { return -1; } return row; } gd::RectF SignalPanel::RowRect(int row) const { const float rowHeight = kRowHeight * m_scale; const float top = m_listRect.Y + static_cast(row) * rowHeight - m_scrollOffset; return gd::RectF(m_listRect.X, top, m_listRect.Width, rowHeight); } void SignalPanel::Layout(const gd::RectF& area) { m_area = area; m_headerRect = gd::RectF(area.X, area.Y, area.Width, kHeaderHeight * m_scale); const float styleHeight = kStyleHeight * m_scale; m_styleRect = gd::RectF(area.X, area.Y + area.Height - styleHeight, area.Width, styleHeight); m_listRect = gd::RectF(area.X, m_headerRect.Y + m_headerRect.Height, area.Width, std::max(0.0f, m_styleRect.Y - m_headerRect.Y - m_headerRect.Height)); // 标题区右侧的“全部显示/隐藏”按钮。 const float toggleWidth = 68.0f * m_scale; const float toggleHeight = 22.0f * m_scale; m_toggleAllRect = gd::RectF(area.X + area.Width - kPadding * m_scale - toggleWidth, m_headerRect.Y + (m_headerRect.Height - toggleHeight) * 0.5f, toggleWidth, toggleHeight); // 样式编辑区布局。 const float labelWidth = kLineLabelWidth * m_scale; m_widthRowLabelX = m_styleRect.X + kPadding * m_scale + labelWidth; m_styleRowHeight = 30.0f * m_scale; const float contentWidth = m_styleRect.Width - kPadding * 2.0f * m_scale - labelWidth; const float contentLeft = m_widthRowLabelX; int paletteCount = 0; const std::uint32_t* palette = SignalPalette(paletteCount); (void)palette; m_colorCount = std::min(paletteCount, static_cast(m_colorRects.size())); // 色块尺寸按可用宽度自适应,避免超出面板。 const float colorGap = 4.0f * m_scale; float swatchSize = 20.0f * m_scale; if (m_colorCount > 1) { const float fitted = (contentWidth - colorGap * static_cast(m_colorCount - 1)) / static_cast(m_colorCount); if (fitted < swatchSize) { swatchSize = std::max(10.0f * m_scale, fitted); } } const float colorRowTop = m_styleRect.Y + 30.0f * m_scale; for (int index = 0; index < m_colorCount; ++index) { const float x = contentLeft + static_cast(index) * (swatchSize + colorGap); m_colorRects[static_cast(index)] = gd::RectF(x, colorRowTop, swatchSize, swatchSize); } const float widthRowTop = colorRowTop + swatchSize + 12.0f * m_scale; const float widthGap = 6.0f * m_scale; const float widthButtonWidth = std::min(56.0f * m_scale, (contentWidth - widthGap * 3.0f) / 4.0f); for (int index = 0; index < 4; ++index) { const float x = contentLeft + static_cast(index) * (widthButtonWidth + widthGap); m_widthRects[static_cast(index)] = gd::RectF(x, widthRowTop, widthButtonWidth, 26.0f * m_scale); } const float modeRowTop = widthRowTop + 26.0f * m_scale + 10.0f * m_scale - 2.0f * m_scale; const float modeGap = 6.0f * m_scale; const float modeButtonWidth = (contentWidth - modeGap * 3.0f) / 4.0f; for (int index = 0; index < 4; ++index) { const float x = contentLeft + static_cast(index) * (modeButtonWidth + modeGap); m_modeRects[static_cast(index)] = gd::RectF(x, modeRowTop, modeButtonWidth, 26.0f * m_scale); } const float laneRowTop = modeRowTop + 26.0f * m_scale + 14.0f * m_scale; m_laneButtonRect = gd::RectF(contentLeft, laneRowTop, std::min(contentWidth, 150.0f * m_scale), 26.0f * m_scale); ClampScroll(); } void SignalPanel::Paint(gd::Graphics& g) { if (m_theme == nullptr || m_fonts == nullptr) { return; } UiFillRect(g, m_area, m_theme->panelBg); // 标题区。 { const std::wstring title = (m_document == nullptr) ? std::wstring(L"信号") : (L"信号 · " + FormatCount(static_cast(m_document->Signals().size()))); gd::RectF titleRect(m_headerRect.X + kPadding * m_scale, m_headerRect.Y, m_headerRect.Width - kPadding * 2.0f * m_scale, m_headerRect.Height); UiText(g, title, m_fonts->uiBold, titleRect, m_theme->textStrong, TextAlign::LEFT); const bool allVisible = AreAllSignalsVisible(); const std::wstring toggleLabel = allVisible ? L"全部隐藏" : L"全部显示"; const bool toggleHover = (m_hoverStyle == StyleHit::TOGGLE_ALL); if (toggleHover) { UiFillRoundRect(g, m_toggleAllRect, kCorner * m_scale, m_theme->hover); } UiDrawRoundRect(g, m_toggleAllRect, kCorner * m_scale, m_theme->border, 1.0f); UiText(g, toggleLabel, m_fonts->uiSmall, m_toggleAllRect, (m_document == nullptr) ? m_theme->textMuted : m_theme->text, TextAlign::CENTER); } // 列表区。 { const int saveState = g.Save(); g.SetClip(gd::RectF(m_listRect.X, m_listRect.Y, m_listRect.Width, m_listRect.Height)); if (m_document == nullptr || m_document->Signals().empty()) { UiText(g, L"尚未加载数据,请打开 CSV 文件", m_fonts->uiSmall, m_listRect, m_theme->textMuted, TextAlign::CENTER); } else { const int rowCount = static_cast(m_document->Signals().size()); for (int row = 0; row < rowCount; ++row) { const gd::RectF rect = RowRect(row); if (rect.Y + rect.Height < m_listRect.Y || rect.Y > m_listRect.Y + m_listRect.Height) { continue; } PaintRow(g, row); } } g.Restore(saveState); } PaintStyleSection(g); // 分隔线。 UiDrawLine(g, m_area.X + m_area.Width - 1.0f, m_area.Y, m_area.X + m_area.Width - 1.0f, m_area.Y + m_area.Height, m_theme->border, 1.0f); UiDrawLine(g, m_headerRect.X, m_headerRect.Y + m_headerRect.Height - 1.0f, m_headerRect.X + m_headerRect.Width, m_headerRect.Y + m_headerRect.Height - 1.0f, m_theme->border, 1.0f); if (m_styleRect.Height > 0.0f) { UiDrawLine(g, m_styleRect.X, m_styleRect.Y, m_styleRect.X + m_styleRect.Width, m_styleRect.Y, m_theme->border, 1.0f); } } void SignalPanel::PaintRow(gd::Graphics& g, int row) { if (m_document == nullptr || row < 0 || row >= static_cast(m_document->Signals().size())) { return; } const Signal& signal = m_document->Signals()[static_cast(row)]; const gd::RectF rect = RowRect(row); const bool selected = (m_document->SelectedSignal() == row); if (selected) { UiFillRect(g, rect, m_theme->selection); UiFillRect(g, gd::RectF(rect.X, rect.Y, 2.0f * m_scale, rect.Height), m_theme->accent); } else if (m_hoverRow == row) { UiFillRect(g, rect, m_theme->hover); } const float centerY = rect.Y + rect.Height * 0.5f; // 复选框。 const gd::RectF checkRect(rect.X + kPadding * m_scale, centerY - kCheckSize * m_scale * 0.5f, kCheckSize * m_scale, kCheckSize * m_scale); if (signal.visible) { UiFillRoundRect(g, checkRect, 3.0f * m_scale, m_theme->accent); gd::Pen pen(ToGdiColor(m_theme->textOnAccent), 1.6f * m_scale); pen.SetStartCap(gd::LineCapRound); pen.SetEndCap(gd::LineCapRound); g.DrawLine(&pen, checkRect.X + 3.0f * m_scale, centerY, checkRect.X + 6.0f * m_scale, checkRect.Y + checkRect.Height - 4.0f * m_scale); g.DrawLine(&pen, checkRect.X + 6.0f * m_scale, checkRect.Y + checkRect.Height - 4.0f * m_scale, checkRect.X + checkRect.Width - 3.0f * m_scale, checkRect.Y + 4.0f * m_scale); } else { UiFillRoundRect(g, checkRect, 3.0f * m_scale, m_theme->cardBg); UiDrawRoundRect(g, checkRect, 3.0f * m_scale, m_theme->borderStrong, 1.0f); } // 颜色块。 const gd::RectF swatchRect(checkRect.X + checkRect.Width + 10.0f * m_scale, centerY - kSwatchSize * m_scale * 0.5f, kSwatchSize * m_scale, kSwatchSize * m_scale); UiFillRoundRect(g, swatchRect, 2.5f * m_scale, signal.visible ? signal.color : WithAlpha(signal.color, 0x55)); // 名称。 const float badgeOffset = kBadgeWidth * m_scale + 8.0f * m_scale; const float nameLeft = swatchRect.X + swatchRect.Width + 10.0f * m_scale; const float nameWidth = rect.X + rect.Width - kPadding * m_scale - badgeOffset - nameLeft; const std::wstring nameText = UiEllipsize(g, signal.name, m_fonts->ui, nameWidth); UiText(g, nameText, m_fonts->ui, gd::RectF(nameLeft, rect.Y, nameWidth, rect.Height), signal.visible ? m_theme->text : m_theme->textMuted, TextAlign::LEFT); // 网格标记。 const int laneIndex = m_document->LaneOfSignal(row); if (laneIndex >= 0) { const gd::RectF badgeRect(rect.X + rect.Width - kPadding * m_scale - kBadgeWidth * m_scale, centerY - kBadgeHeight * m_scale * 0.5f, kBadgeWidth * m_scale, kBadgeHeight * m_scale); const bool laneActive = (selected && m_hoverStyle == StyleHit::LANE); UiFillRoundRect(g, badgeRect, kBadgeHeight * m_scale * 0.5f, laneActive ? m_theme->accentSoft : m_theme->cardBg); UiDrawRoundRect(g, badgeRect, kBadgeHeight * m_scale * 0.5f, m_theme->border, 1.0f); UiText(g, L"G" + std::to_wstring(laneIndex + 1), m_fonts->monoSmall, badgeRect, m_theme->text, TextAlign::CENTER); } } void SignalPanel::PaintStyleSection(gd::Graphics& g) { if (m_styleRect.Height <= 0.0f) { return; } UiFillRect(g, m_styleRect, m_theme->panelBg); const int selectedIndex = (m_document == nullptr) ? -1 : m_document->SelectedSignal(); if (m_document == nullptr || selectedIndex < 0 || selectedIndex >= static_cast(m_document->Signals().size())) { UiText(g, L"请选择信号以编辑样式", m_fonts->uiSmall, m_styleRect, m_theme->textMuted, TextAlign::CENTER); return; } const Signal& signal = m_document->Signals()[static_cast(selectedIndex)]; // 标题行。 const gd::RectF titleRect(m_styleRect.X + kPadding * m_scale, m_styleRect.Y, m_styleRect.Width - kPadding * 2.0f * m_scale, 28.0f * m_scale); UiText(g, L"样式 · " + signal.name, m_fonts->uiBold, titleRect, m_theme->textStrong, TextAlign::LEFT); const float labelX = m_styleRect.X + kPadding * m_scale; const float labelWidth = kLineLabelWidth * m_scale; // 颜色。 { const float rowTop = m_styleRect.Y + 30.0f * m_scale; UiText(g, L"颜色", m_fonts->uiSmall, gd::RectF(labelX, rowTop, labelWidth, 20.0f * m_scale), m_theme->textMuted, TextAlign::LEFT); int paletteCount = 0; const std::uint32_t* palette = SignalPalette(paletteCount); for (int index = 0; index < m_colorCount; ++index) { const gd::RectF& rect = m_colorRects[static_cast(index)]; const std::uint32_t color = palette[index % paletteCount]; const bool hovered = (m_hoverStyle == StyleHit::COLOR && m_hoverStyleIndex == index); const bool active = (signal.color == color); UiFillRoundRect(g, rect, 4.0f * m_scale, color); if (active || hovered) { const gd::RectF ring(rect.X - 2.0f * m_scale, rect.Y - 2.0f * m_scale, rect.Width + 4.0f * m_scale, rect.Height + 4.0f * m_scale); UiDrawRoundRect(g, ring, 5.0f * m_scale, active ? m_theme->textStrong : m_theme->borderStrong, active ? 1.6f : 1.0f); } } } // 线宽。 { const gd::RectF& first = m_widthRects[0]; UiText(g, L"线宽", m_fonts->uiSmall, gd::RectF(labelX, first.Y, labelWidth, first.Height), m_theme->textMuted, TextAlign::LEFT); for (int index = 0; index < 4; ++index) { const gd::RectF& rect = m_widthRects[static_cast(index)]; const bool active = std::fabs(signal.lineWidth - kWidthOptions[index]) < 0.01f; const bool hovered = (m_hoverStyle == StyleHit::WIDTH && 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; const float lineWidth = kWidthOptions[index] * m_scale; UiDrawLine(g, rect.X + 8.0f * m_scale, centerY, rect.X + rect.Width - 8.0f * m_scale, centerY, active ? m_theme->accent : m_theme->text, lineWidth); } } // 绘制方式。 { const gd::RectF& first = m_modeRects[0]; UiText(g, L"方式", m_fonts->uiSmall, gd::RectF(labelX, first.Y, labelWidth, first.Height), m_theme->textMuted, TextAlign::LEFT); for (int index = 0; index < 4; ++index) { const gd::RectF& rect = m_modeRects[static_cast(index)]; const bool active = (signal.mode == kModeOptions[index]); const bool hovered = (m_hoverStyle == StyleHit::MODE && 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); UiText(g, DrawModeName(kModeOptions[index]), m_fonts->uiSmall, rect, active ? m_theme->textStrong : m_theme->text, TextAlign::CENTER); } } // 所属网格。 { UiText(g, L"网格", m_fonts->uiSmall, gd::RectF(labelX, m_laneButtonRect.Y, labelWidth, m_laneButtonRect.Height), m_theme->textMuted, TextAlign::LEFT); const bool hovered = (m_hoverStyle == StyleHit::LANE); UiFillRoundRect(g, m_laneButtonRect, kCorner * m_scale, hovered ? m_theme->hover : m_theme->cardBg); UiDrawRoundRect(g, m_laneButtonRect, kCorner * m_scale, m_theme->border, 1.0f); const int laneIndex = m_document->LaneOfSignal(selectedIndex); std::wstring label = (laneIndex >= 0) ? (L"网格 " + std::to_wstring(laneIndex + 1)) : L"未分配"; const gd::RectF textRect(m_laneButtonRect.X + 8.0f * m_scale, m_laneButtonRect.Y, m_laneButtonRect.Width - 26.0f * m_scale, m_laneButtonRect.Height); UiText(g, label, m_fonts->uiSmall, textRect, m_theme->text, TextAlign::LEFT); // 下拉箭头。 const float arrowX = m_laneButtonRect.X + m_laneButtonRect.Width - 16.0f * m_scale; const float arrowY = m_laneButtonRect.Y + m_laneButtonRect.Height * 0.5f; gd::PointF points[3]; points[0] = gd::PointF(arrowX - 4.0f * m_scale, arrowY - 2.0f * m_scale); points[1] = gd::PointF(arrowX + 4.0f * m_scale, arrowY - 2.0f * m_scale); points[2] = gd::PointF(arrowX, arrowY + 3.0f * m_scale); gd::SolidBrush brush(ToGdiColor(m_theme->textMuted)); g.FillPolygon(&brush, points, 3); } } bool SignalPanel::AreAllSignalsVisible() const { if (m_document == nullptr || m_document->Signals().empty()) { return false; } for (const Signal& signal : m_document->Signals()) { if (!signal.visible) { return false; } } return true; } bool SignalPanel::OnMouseMove(int x, int y) { const int hoverRow = RowAtY(y); StyleHit hoverStyle = StyleHit::NONE; int hoverStyleIndex = -1; const float fx = static_cast(x); const float fy = static_cast(y); if (UiPointInRect(m_toggleAllRect, fx, fy)) { hoverStyle = StyleHit::TOGGLE_ALL; } else if (m_document != nullptr && m_document->SelectedSignal() >= 0) { for (int index = 0; index < m_colorCount; ++index) { if (UiPointInRect(m_colorRects[static_cast(index)], fx, fy)) { hoverStyle = StyleHit::COLOR; hoverStyleIndex = index; break; } } if (hoverStyle == StyleHit::NONE) { for (int index = 0; index < 4; ++index) { if (UiPointInRect(m_widthRects[static_cast(index)], fx, fy)) { hoverStyle = StyleHit::WIDTH; hoverStyleIndex = index; break; } } } if (hoverStyle == StyleHit::NONE) { for (int index = 0; index < 4; ++index) { if (UiPointInRect(m_modeRects[static_cast(index)], fx, fy)) { hoverStyle = StyleHit::MODE; hoverStyleIndex = index; break; } } } if (hoverStyle == StyleHit::NONE && UiPointInRect(m_laneButtonRect, fx, fy)) { hoverStyle = StyleHit::LANE; } } if (hoverRow == m_hoverRow && hoverStyle == m_hoverStyle && hoverStyleIndex == m_hoverStyleIndex) { return false; } m_hoverRow = hoverRow; m_hoverStyle = hoverStyle; m_hoverStyleIndex = hoverStyleIndex; return true; } bool SignalPanel::OnLButtonDown(int x, int y) { if (m_document == nullptr) { return false; } const float fx = static_cast(x); const float fy = static_cast(y); // 全部显示/隐藏。 if (UiPointInRect(m_toggleAllRect, fx, fy)) { m_document->SetAllVisible(!AreAllSignalsVisible()); return true; } // 样式编辑区。 const int selectedIndex = m_document->SelectedSignal(); if (selectedIndex >= 0 && selectedIndex < static_cast(m_document->Signals().size())) { Signal& signal = m_document->Signals()[static_cast(selectedIndex)]; int paletteCount = 0; const std::uint32_t* palette = SignalPalette(paletteCount); for (int index = 0; index < m_colorCount; ++index) { if (UiPointInRect(m_colorRects[static_cast(index)], fx, fy)) { signal.color = palette[index % paletteCount]; return true; } } for (int index = 0; index < 4; ++index) { if (UiPointInRect(m_widthRects[static_cast(index)], fx, fy)) { signal.lineWidth = kWidthOptions[index]; return true; } } for (int index = 0; index < 4; ++index) { if (UiPointInRect(m_modeRects[static_cast(index)], fx, fy)) { signal.mode = kModeOptions[index]; return true; } } if (UiPointInRect(m_laneButtonRect, fx, fy)) { ShowLaneMenu(); return true; } } // 列表行。 const int row = RowAtY(y); if (row >= 0) { const gd::RectF rect = RowRect(row); const gd::RectF checkRect(rect.X + kPadding * m_scale, rect.Y + (rect.Height - kCheckSize * m_scale) * 0.5f, kCheckSize * m_scale, kCheckSize * m_scale); if (UiPointInRect(checkRect, fx, fy)) { Signal& signal = m_document->Signals()[static_cast(row)]; signal.visible = !signal.visible; m_document->SetSelectedSignal(row); return true; } m_document->SetSelectedSignal(row); return true; } return false; } bool SignalPanel::OnLButtonUp(int /*x*/, int /*y*/) { return false; } bool SignalPanel::OnDoubleClick(int x, int y) { if (m_document == nullptr) { return false; } const int row = RowAtY(y); if (row < 0) { return false; } Signal& signal = m_document->Signals()[static_cast(row)]; signal.visible = !signal.visible; m_document->SetSelectedSignal(row); return true; } bool SignalPanel::OnRButtonDown(int x, int y) { if (m_document == nullptr) { return false; } const int row = RowAtY(y); if (row < 0) { return false; } m_document->SetSelectedSignal(row); POINT cursor = {x, y}; if (m_host != nullptr) { ClientToScreen(m_host, &cursor); } ShowSignalMenu(row, cursor.x, cursor.y); return true; } bool SignalPanel::OnMouseWheel(int delta) { if (m_document == nullptr) { return false; } const float previous = m_scrollOffset; m_scrollOffset -= static_cast(delta) / 120.0f * 54.0f * m_scale; ClampScroll(); return std::fabs(previous - m_scrollOffset) > 0.01f; } bool SignalPanel::OnMouseLeave() { if (m_hoverRow < 0 && m_hoverStyle == StyleHit::NONE) { return false; } m_hoverRow = -1; m_hoverStyle = StyleHit::NONE; m_hoverStyleIndex = -1; return true; } void SignalPanel::ShowLaneMenu() { if (m_document == nullptr || m_host == nullptr) { return; } const int selectedIndex = m_document->SelectedSignal(); if (selectedIndex < 0) { return; } HMENU menu = CreatePopupMenu(); if (menu == nullptr) { return; } const int currentLane = m_document->LaneOfSignal(selectedIndex); const std::vector& lanes = m_document->Lanes(); for (std::size_t index = 0; index < lanes.size(); ++index) { std::wstring label = L"网格 " + std::to_wstring(index + 1) + L"(" + std::to_wstring(lanes[index].signalIndices.size()) + L" 条信号)"; AppendMenuW(menu, MF_STRING, static_cast(kMenuLaneBase + index), label.c_str()); } AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); AppendMenuW(menu, MF_STRING, static_cast(kMenuLaneNew), L"新建网格"); if (currentLane >= 0 && currentLane < static_cast(lanes.size())) { CheckMenuRadioItem(menu, static_cast(kMenuLaneBase), static_cast(kMenuLaneBase + lanes.size() - 1), static_cast(kMenuLaneBase + currentLane), MF_BYCOMMAND); } POINT anchor = {static_cast(m_laneButtonRect.X), static_cast(m_laneButtonRect.Y)}; ClientToScreen(m_host, &anchor); SetForegroundWindow(m_host); const int command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, anchor.x, anchor.y + static_cast(m_laneButtonRect.Height), 0, m_host, nullptr); DestroyMenu(menu); if (command >= kMenuLaneBase && command < kMenuLaneNew) { m_document->MoveSignalToLane(selectedIndex, command - kMenuLaneBase); } else if (command == kMenuLaneNew) { m_document->MoveSignalToLane(selectedIndex, -1); } } void SignalPanel::ShowSignalMenu(int signalIndex, int screenX, int screenY) { if (m_document == nullptr || m_host == nullptr) { return; } if (signalIndex < 0 || signalIndex >= static_cast(m_document->Signals().size())) { return; } Signal& signal = m_document->Signals()[static_cast(signalIndex)]; HMENU menu = CreatePopupMenu(); if (menu == nullptr) { return; } AppendMenuW(menu, MF_STRING, static_cast(kMenuToggleVisible), signal.visible ? L"隐藏该信号" : L"显示该信号"); AppendMenuW(menu, MF_STRING, static_cast(kMenuSolo), L"仅显示该信号"); AppendMenuW(menu, MF_STRING, static_cast(kMenuShowAll), L"显示全部信号"); AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); HMENU colorMenu = CreatePopupMenu(); int paletteCount = 0; const std::uint32_t* palette = SignalPalette(paletteCount); const wchar_t* colorNames[] = {L"蓝", L"绿", L"红", L"紫", L"黄", L"青", L"品红", L"橙", L"深灰", L"灰"}; for (int index = 0; index < SignalColorCount(); ++index) { const std::uint32_t color = palette[index % paletteCount]; std::wstring label = (index < 10) ? std::wstring(colorNames[index]) : (L"颜色 " + std::to_wstring(index + 1)); AppendMenuW(colorMenu, MF_STRING, static_cast(kMenuColorBase + index), label.c_str()); if (signal.color == color) { CheckMenuRadioItem(colorMenu, static_cast(kMenuColorBase), static_cast(kMenuColorBase + SignalColorCount() - 1), static_cast(kMenuColorBase + index), MF_BYCOMMAND); } } AppendMenuW(menu, MF_POPUP, reinterpret_cast(colorMenu), L"颜色"); HMENU widthMenu = CreatePopupMenu(); for (int index = 0; index < 4; ++index) { const std::wstring label = FormatNumber(kWidthOptions[index], 1) + L" px"; AppendMenuW(widthMenu, MF_STRING, static_cast(kMenuWidthBase + index), label.c_str()); if (std::fabs(signal.lineWidth - kWidthOptions[index]) < 0.01f) { CheckMenuRadioItem(widthMenu, static_cast(kMenuWidthBase), static_cast(kMenuWidthBase + 3), static_cast(kMenuWidthBase + index), MF_BYCOMMAND); } } AppendMenuW(menu, MF_POPUP, reinterpret_cast(widthMenu), L"线宽"); HMENU modeMenu = CreatePopupMenu(); for (int index = 0; index < 4; ++index) { AppendMenuW(modeMenu, MF_STRING, static_cast(kMenuModeBase + index), DrawModeName(kModeOptions[index])); if (signal.mode == kModeOptions[index]) { CheckMenuRadioItem(modeMenu, static_cast(kMenuModeBase), static_cast(kMenuModeBase + 3), static_cast(kMenuModeBase + index), MF_BYCOMMAND); } } AppendMenuW(menu, MF_POPUP, reinterpret_cast(modeMenu), L"显示方式"); HMENU laneMenu = CreatePopupMenu(); const std::vector& lanes = m_document->Lanes(); const int currentLane = m_document->LaneOfSignal(signalIndex); for (std::size_t index = 0; index < lanes.size(); ++index) { const std::wstring laneLabel = L"网格 " + std::to_wstring(index + 1); AppendMenuW(laneMenu, MF_STRING, static_cast(kMenuLaneBase + index), laneLabel.c_str()); } AppendMenuW(laneMenu, MF_SEPARATOR, 0, nullptr); AppendMenuW(laneMenu, MF_STRING, static_cast(kMenuLaneNew), L"新建网格"); if (currentLane >= 0 && !lanes.empty()) { CheckMenuRadioItem(laneMenu, static_cast(kMenuLaneBase), static_cast(kMenuLaneBase + lanes.size() - 1), static_cast(kMenuLaneBase + currentLane), MF_BYCOMMAND); } AppendMenuW(menu, MF_POPUP, reinterpret_cast(laneMenu), L"所属网格"); SetForegroundWindow(m_host); const int command = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, screenX, screenY, 0, m_host, nullptr); DestroyMenu(menu); if (command == kMenuToggleVisible) { signal.visible = !signal.visible; } else if (command == kMenuSolo) { m_document->SoloSignal(signalIndex); } else if (command == kMenuShowAll) { m_document->SetAllVisible(true); } else if (command >= kMenuModeBase && command < kMenuModeBase + 4) { signal.mode = kModeOptions[command - kMenuModeBase]; } else if (command >= kMenuWidthBase && command < kMenuWidthBase + 4) { signal.lineWidth = kWidthOptions[command - kMenuWidthBase]; } else if (command >= kMenuColorBase && command < kMenuColorBase + SignalColorCount()) { signal.color = palette[(command - kMenuColorBase) % paletteCount]; } else if (command >= kMenuLaneBase && command < kMenuLaneNew) { m_document->MoveSignalToLane(signalIndex, command - kMenuLaneBase); } else if (command == kMenuLaneNew) { m_document->MoveSignalToLane(signalIndex, -1); } } } // namespace jdi